rust 初探 – 通用编程概念
变量和可变性
- 声明变量使用 let 关键值
- 默认情况下,变量是不可变的(immutable),可以在变量前面加上 mut,就可以使变量可变
变量和常量
- 常量(constant):常量在绑定值之后是不可变的,但是它和不可变的变量不同:
- 不可以使用 mut,常量永远都是不可变的
- 声明常量使用 const 关键字,它的类型必须被标注
- 常量可以在任何作用域内进行声明,包括全局作用域
- 常量只能绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值
- 在程序运行期间,常量在其声明的作用域里一直有效
- 命名规范:rust 常量使用全大写字母,每个单词之间用下划线分开:
const MAX_POINT:u32 = 100
Shadowing
- 可以使用相同的名字声明新的变量,新的变量就会 shadow 之前声明的同名变量
- 在后续的代码汇总这个变量名代表的就是新的变量
fn main() {
let x = 5;
let x = x + 1;
println!("x is {}", x); // x is 6
}
运行结果:
hope@hopedeMacBook-Air hello_cargo % cargo run
Compiling hello_cargo v0.1.0 (/Users/hope/rust/hello_cargo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/hello_cargo`
x is 6
- shadow 和把变量标记为 mut 是不一样的:
- 如果不使用 let 关键字,那么重新给非 mut 的变量赋值就会导致编译时错误
- 而使用 let 声明的同名新变量,也是不可变的
- 使用 let 声明的同名新变量,它的类型可以与之前不同
fn main() {
let x = "string";
let x = x.len();
println!("x is {}", x);
}
运行结果:
hope@hopedeMacBook-Air hello_cargo % cargo run
Compiling hello_cargo v0.1.0 (/Users/hope/rust/hello_cargo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/hello_cargo`
x is 6
数据类型:标量类型
一个标量类型代表一个单个的值
- rust 是静态编译语言,在编译时必须知道所有变量的类型
- 基于使用的值,编译器通常能够推断出它的具体类型
- 但如果可能的类型比较多,就必须添加类型的标注,否则编译会报错
fn main() {
let guess = "42".parse().expect("not a number");
// let guess :i32 = "42".parse().expect("not a number"); // 正确的写法
println!("guess is {}", guess);
}
Compiling hello_cargo v0.1.0 (/Users/hope/rust/hello_cargo)
error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("not a number");
| ^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
|
2 | let guess: /* Type */ = "42".parse().expect("not a number");
| ++++++++++++
For more information about this error, try `rustc --explain E0284`.
error: could not compile `hello_cargo` (bin "hello_cargo") due to 1 previous error
整数类型
- 有符号范围:-2n-1 ~ (2n-1-1)
- 无符号范围:0 ~ (2n-1)
- arch:代表系统架构,32位或者64位
整数字面量
- 除了 byte 类型外,所有数值字面值都允许使用类型后缀(如:57u8)
- 整数的默认类型就是 i32
整数溢出
- 调试模式下编译:会检查整数溢出,如果发生溢出,会在运行时panic
- 发布模式下不会检查可能导致 panic 的整数溢出,会执行“环绕” 操作(u8 的256 变成 0)
浮点类型
- f32,32位,单精度
- f64,64位,双精度
- f64 是默认类型,因为在现代 CPU 上 f64 和 f32 速度差不多,而且精度更高
布尔类型
- 有两个值:true ,false
- 一个字节大小,符号是 bool
字符类型
- char 类型被用来描述语言中最基础的单个字符
- 字符类型的字面值使用单引号
- 占用 4 字节大小
- 是 unicode 标量值,可以表示比 ASCII 多得多的字符内容
- 但是 unicode 中并没有“字符”的概念,所以直觉上认为的字符也许与 rust 中的概念并不相符
数据类型:复合类型
复合类型可以将多个值放到一个类型里面,rust 中提供了两种基础的复合类型:元组(Tuple),数组
Tuple
- Tuple 可以将多个类型的多个值放到一个类型里
- Tuple 的长度是固定的:一旦声明就无法改变
- 创建 Tuple:在小括号里,将值用逗号分开
- Tuple 中的每个位置都对应一个类型,Tuple 中各元素的类型不必相同
fn main() {
let tup:(i32, f64, u8) = (500, 6.7, 1);
println!("{}, {}, {}", tup.0, tup.1, tup.2); // 使用点标记法,获取
}
可以使用模式匹配来解构一个 Tuple 来获取元素的值
fn main() {
let tup:(i32, f64, u8) = (500, 6.7, 1);
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);
}
数组
- 数组也可以多个值放在一个类型里
- 数组中的每个元素的类型必须相同
- 数组的长度也是固定的
- 声明:在中括号里,使用逗号分开
数组的用处
- 想让数据存放在 stack 上而不是 heap 上,或者想保证有固定数量的元素,使用数组更有好处
- 数组没有 Vector 灵活,Vector 和数组类似,由标准库提供
- Vector 的长度是可以改变的
数组的类型
- 例如:let a:[i32;5] = [1,2,3,4,5];
- 例如:let a:[1;5];相当于let a:[i32;5] = [1,1,1,1,1];
数组的访问
使用索引访问即可,如果访问的索引超过了数组的范围,编译不会通过,运行也会报错
函数
- 声明函数使用 fn 关键字
- 针对函数和变量名,rust 使用 snake case 命名规范:所有字母都小写,单词之间使用下划线分开
函数的参数
- 函数中定义的 parameters,调用时传入的 arguments
- 函数签名里,必须声明函数的类型,多个参数必须单独声明
函数体中的语句与表达式
- 函数体由一系列语句组成,可选的由一个表达式结束
- rust 是一个基于表达式的语言
- 语句是执行一些动作的指令
- 表达式会计算产生一个值
- 函数的定义也是语句
- 语句不返回值,所以不可以使用let 将一个语句赋给一个变量
fn main() {
let x = 5;
let y = {
let x = 1;
x + 3 // 这时候是 4
};
// let y = {
// let x = 1;
// x + 3; // 语句,这时候是一个空的 ()
// };
println!("{}", y);
}
函数的返回值
- 在 -> 符号后面声明函数返回值的类型,但是不可以为返回值命名
- 在 rust 里吗,返回值就是函数体里吗最后一个表达式的值
- 若想提前返回,需使用 return 关键字,并指定一个值(默认使用最后一个表达式作为返回值)
fn main() {
let x = add(1);
println!("{}", x); // 2
}
fn add(x: i32) -> i32 {
x + 1
}
控制流:if else
if 表达式
- 和 go 一样,但是如果使用了多个 else if,建议使用 match 来重构代码
控制流:循环
loop
- 一直执行,直到主动停止
while
- 每次执行,都需要判断一次条件
for
- 一般用来遍历
fn main() {
let a = [1, 2, 3, 4];
for v in a.iter() {
println!("{}", v); // 1 2 3 4
}
}
fn main() {
// let a = [1, 2, 3, 4];
// for v in a.iter() {
// println!("{}", v);
// }
for v in (1..4).rev() {
println!("{}", v); // 反转 3 2 1
}
}