Rust 基本语法

变量

整数

  • 无符号整数以u开头
  • 有符号整数以i开头
  • 对于Rust默认整数是i32
  • 对于整数溢出
    • 开发模式中编译会检测溢出,如果溢出会导致程序panic
    • 发布模式中编译不会检查可能会导致的溢出,如果运行时发生溢出,会执行环绕操作保证数值在范围内且程序不会panic

单精度浮点类型

  • f32

双精度浮点类型

  • f64
  • Rust采用f64作为默认的浮点类型

布尔类型

  • bool:占用1字节大小

字符类型

  • char:占用4字节大小
  • 使用单引号

复合类型

  • 元组(Tuple):可以将多个类型的多个值放在一个类型里,长度固定一旦创建无法修改

    • 创建:在小括号中,将值用逗号分开
    • Tuple中的每一个位置都对应一个类型,Tuple中各元素的类型不必相同
    • 访问Tuple元素使用 .下标 进行访问
  • 数组:数组元素类型必须相同,数组长度固定,在栈上存储

    • 创建:在中括号中,将值用逗号分开
    rust 复制代码
    let a:[i32; 5] = [1, 2, 3, 4, 5];
    let a = [3; 5];// 等于let a = [3, 3, 3, 3, 3];
    • 使用 [下标] 访问数组元素
    • 下标越界编译会通过,但是运行会panic

字符串

  • String在堆内存上分配,能够存储在编译时未知数量的文本
  • String类型的值可以修改,但是字符串字面值不能修改,因为字符串字面值在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里(速度快,高效,是因为它的不可变性),对于String类型,为了支持可变性,需要在堆内存上分配内存来保存编译时未知的文本内容(操作系统必须在运行时来请求内存,这步通过调用String::from来实现,当用完String后,需要使用某种方式将内存返回给操作系统)
rust 复制代码
let mut s = String::from("abcdefg");
s.push_str("hi");
// s -> abcdefghi
  • 一个String由三个部分组成(这部分存放在栈中,字符串内容存放在堆中)
    • 指向内容的指针
    • 长度(存放字符串内容所需的字节数)
    • 容量(从操作系统获得的总字节数)
rust 复制代码
let s1 = String::from("123");
let s2 = s1;
// s1将不能再被使用
rust 复制代码
let s1 = String::from("123");
let s2 = s1.clone();
// s1和s2独立

Copy trait

  • 如果一个类型实现了Copy这个trait,那么旧的变量在赋值后任然可以使用
  • 如果一个类型或者该类型的一部分实现了Drop trait,那么Rust不允许让它再去实现Copy trait了

一些拥有Copy trait的类型

  • 任何简单标量的组合类型都可以是Copy的
  • 任何需要分配内存或者某种资源的都不是Copy的
  • 一些拥有Copy trait的类型:
    • 所有的整数类型,例如u32
    • bool
    • char
    • 所有的浮点类型
    • Tuple(元组)要求其所有字段都是Copy的

函数

声明

使用 fn 关键字

Rust命名规范(snake case):

  • 针对函数和变量名,所有字母都是小写,单词之间使用下划线分开

参数

  • 在函数签名里,必须声明每个参数的类型

语句/表达式

  • 函数体由一系列语句组成,可选的由一个表达式结束
  • Rust是一个基于表达式的语言
  • 语句是执行一些动作的指令
  • 表达式会计算产生一个值
  • 函数的定义也是语句
  • 语句没有返回值,所以不可以使用let将一个语句赋给一个变量
rust 复制代码
// 块表达式
let y = {
    let x = 1;
    x + 3
}
// y等于块表达式最后一个值即x + 3 = 4
// 如果x + 3后加上分号,那么x + 3就变成语句了,分号后面没有返回值,默认就是空的Tuple即()
rust 复制代码
fn f() -> i32{
   5
}

fn main(){
   let res = f();// res为5
}

返回值

  • -> 符号后边声明函数返回值的类型,但是不可以为返回值命名
  • 在Rust里面,返回值就是函数体最后一个表达式的值
  • 若想提前返回,需使用 return 关键字,并指定一个值
  • 大多数函数都是默认使用最后一个表达式作为返回值

注释

  • 单行注释
rust 复制代码
// 这是单行注释
  • 多行注释
rust 复制代码
// 这是
// 多行注释

/**
 * 这是
 * 多行注释
**/

控制流

if表达式

  • if 表达式 的条件必须是bool类型,否则会报错(Rust不会自动类型转换)
rust 复制代码
if 3 < 5{
   // true
}else{
   // false
}
rs 复制代码
let a = if 1 < 3 { 1 } else { 6 };
// a = 1;

loop表达式

  • loop表达式里的代码会一直循环执行,直到break
rust 复制代码
let mut cnt = 0;
let res = loop{
    cnt += 1;
    if cnt == 10 {
        break cnt;
    }
}
// res = 10;

while循环

rust 复制代码
let mut cnt = 100;
while cnt > 0 {
    cnt -= 1;
}
// res = 0;

for循环

  • 安全,简洁,用得较多
rust 复制代码
let a = [1, 2, 3, 4];

for tem in a.iter() {
    println!("{}", tem);
}

Range

  • 标准库提供
  • 指定开始数字和结束数字,Range可以生成他们之间的数字(不含结束)
  • rev方法可以反转Range
rust 复制代码
// (1..4)生成1 - 3 之间的数字
// rev()反转,即 3 - 1之间的数字
// 输出 3 2 1
for tem in (1..4).rev() {
    println!("{}", tem);
}

所有权

  • Rust的核心特性就是所有权
  • 所有程序在运行时都必须管理它们使用计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,他们会不断地寻找不再使用的内存
    • 在其他语言中,程序员必须显示地分配和释放内存
  • Rust采用了第三种方式
    • 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则
    • 当程序运行时,所有权特性不会减慢程序的运行速度

栈内存(Stack)/堆内存(Heap)

  • 栈内存
    • 后进先出
    • 所有存储在栈内存上的数据必须拥有已知的固定大小
      • 编译时大小未知的数据或运行时大小可能发送改变的数据必须放在堆内存上
  • 堆内存
    • 内存组织性差一些
      • 当把数据放入堆内存时,会请求一定数量的空间
      • 操作系统在堆内存里找到一块足够大空间,把他标记为在用,并返回一个指针,也就是这个空间的地址
      • 这个过程叫做在堆内存上进行内存分配,有时仅称为 分配
  • 把值压到栈内存上不叫分配
  • 因为指针是已知固定大小的,可以把指针存放在栈内存上
    • 但如果想要实际数据,必须使用指针来定位
  • 把数据压到栈内存上要比在堆内存上分配快得多
    • 因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在栈内存的顶端
    • 在堆内存上分配空间需要做更多的工作
      • 操作系统需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配
  • 访问堆内存中的数据要比访问栈内存中的数据慢,因为需要通过指针才能找到堆内存中的数据
    • 对于现代的处理器来说,由于缓存的缘故,如果指令在内存中跳转的次数越少,那么速度就越快

函数调用

  • 调用函数时,值被传入到函数(也包括指向堆内存的指针)。函数本地的变量被压到栈内存上。当函数结束后,这些值会从栈内存上弹出。

所有权存在的原因

  • 所有权解决的问题

    • 跟踪代码的哪些部分正在使用堆内存的哪些数据
    • 最小化堆内存上的重复数据量
    • 清理堆内存上未使用的数据以避免空间不足
  • 所有权规则

    • 每个值都有一个变量,这个变量是该值的所有者
    • 每个值同时只能有一个所有者
    • 当所有者超出作用域(scope)时,该值将被删除

返回值与作用域

  • 函数在返回值的过程中同样也会发生所有权的转移
  • 一个变量的所有权总是遵循同样的模式:
    • 把一个值赋值给其他变量时就发生移动
    • 当一个包含堆内存数据的变量离开作用域时,它的值就会被drop函数清理,除非数据的所有权移动到另一个变量上了

引用

  • &表示引用,允许你引用某些值而不取得其所有权

借用

  • 把引用作为函数参数这个行为叫做借用
  • 不可以修改借用的东西
  • 和变量一样,引用默认也是不可变的

可变引用

rust 复制代码
#![allow(warnings)]
use std::io;
use std::error::Error;
use std::boxed::Box;
use std::convert::TryInto;
use std::cmp::Ordering;
use std::cmp::min;
use std::cmp::max;

fn calculate_length(s: &mut String) -> usize {
    s.push_str(" LTPP");
    s.len()
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut s1: String = String::from("SQS");
    println!("{} {}", s1, s1.len());
    let len: usize = calculate_length(&mut s1);
    println!("{} {}", s1, len);
    Ok(())
}

运行结果

rust 复制代码
SQS 3
SQS LTPP 8
  • 在特定作用域内,对于某一块数据,只能有一个可变的引用

    • 好处:在编译时解决数据竞争
  • 以下三种行为会发生数据竞争

    • 两个或多个指针同时访问一个数据
    • 至少有一个指针用于写数据
    • 没有使用任何机制来同步数据的访问
  • 可以通过创建新的作用域,来允许非同时的创建多个可变引用

rust 复制代码
#![allow(warnings)]
use std::io;
use std::error::Error;
use std::boxed::Box;
use std::convert::TryInto;
use std::cmp::Ordering;
use std::cmp::min;
use std::cmp::max;


fn main() -> Result<(), Box<dyn Error>> {
    let mut s1: String = String::from("SQS");
    {
        let s2: String = String::from("LTPP");
        // s2 离开当前作用域后被销毁
    }
    let s1_2 = &mut s1; // s1_2为SQS
    println!("{}", s1_2);
    let s2_2 = &mut s2; // 报错,s2在当前作用域不存在
    println!("{}", s1_2); 
    Ok(())
}

运行结果

rust 复制代码
编译出错!
error[E0425]: cannot find value `s2` in this scope
  --> /main.rs:19:21
   |
19 |     let s2_2 = &mut s2; // 报错,s2在当前作用域不存在
   |                     ^^ help: a local variable with a similar name exists: `s1`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.
  • 不可同时拥有一个可变引用和一个不可变引用
  • 多个不可变的引用是可以的
rust 复制代码
#![allow(warnings)]
use std::io;
use std::error::Error;
use std::boxed::Box;
use std::convert::TryInto;
use std::cmp::Ordering;
use std::cmp::min;
use std::cmp::max;

fn main() -> Result<(), Box<dyn Error>> {
    let mut s: String = String::from("SQS");
    let r1 = &s;
    let r2 = &s;
    let s1 = &mut s;
    println!("{} {} {}", r1, r2, s1);
    Ok(())
}

运行结果

rust 复制代码
编译出错!
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
  --> /main.rs:14:14
   |
12 |     let r1 = &s;
   |              -- immutable borrow occurs here
13 |     let r2 = &s;
14 |     let s1 = &mut s;
   |              ^^^^^^ mutable borrow occurs here
15 |     println!("{} {} {}", r1, r2, s1);
   |                          -- immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

悬空引用

  • 悬空指针:一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了
rust 复制代码
#![allow(warnings)]
use std::io;
use std::error::Error;
use std::boxed::Box;
use std::convert::TryInto;
use std::cmp::Ordering;
use std::cmp::min;
use std::cmp::max;

fn dangle() -> &String {
    let s: String = String::from("SQS");
    &s
}

fn main() -> Result<(), Box<dyn Error>> {
    let r = dangle();
    Ok(())
}

运行结果ust

rust 复制代码
编译出错!
error[E0106]: missing lifetime specifier
  --> /main.rs:10:16
   |
10 | fn dangle() -> &String {
   |                ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
   |
10 | fn dangle() -> &'static String {
   |                 +++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
  • Rust里,编译器可保证引用永远都不是悬空引用

    • 如果引用了某些数据,编译器保证在引用离开作用域之前数据不会离开作用域
  • 引用的规则,任意给定的时刻,只能满足以下情况之一

    • 一个可变的引用
    • 任意数量不可变的引用
相关推荐
C语言魔术师15 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
自由自在的小Bird15 分钟前
简单排序算法
数据结构·算法·排序算法
百流28 分钟前
scala文件编译相关理解
开发语言·学习·scala
Channing Lewis29 分钟前
flask常见问答题
后端·python·flask
Channing Lewis30 分钟前
如何保护 Flask API 的安全性?
后端·python·flask
匹马夕阳1 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?1 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
Evand J2 小时前
matlab绘图——彩色螺旋图
开发语言·matlab·信息可视化
深度混淆2 小时前
C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合
开发语言·c#