变量和 mutable
let:声明变量
let x = 5;
let x: i32 = 5;
// 变量需要被修改就用mut修饰
let mut x = 5
x = 6;
mut:可变性
const:声明常量
// 常量必须声明类型
const A_CONST: i32 = 1;
shadowing: 重复声明变量会隐藏前面的
// 隐藏写法(更符合rust思想)
let x = 5;
let x = x + 1;
let x = x * 2;
println!("x is {}", x); // x is 12
// mutable写法
let mut x = 5;
x = x + 1;
x = x * 2;
println!("x is {}", x); // x is 12
基础数据类型
Rust 是静态,强类型语言,所有变量的类型必须在编译期就明确固定
整数
12 种整数类型 | 长度 | 有符号 | 无符号 | | —- | —- | —- | | 8 bit | i8 | u8 | | 16 bit | i16 | u16 | | 32 bit | i32 | u32 | | 64 bit | i64 | u64 | | 128 bit | i128 | u128 | | arch | isize | usize |
对于未明确标注类型的整数,Rust 默认采用 i32
-
溢出
计算结果大于寄存器或者存储器所能存储或表示的范围
- 编程语言对待溢出的方法:
- crash:程序直接退出
- ignore:最普遍的做法,忽略任何算数溢出
- Rust debug 模式下编译会检查整数溢出并 crash,release 模式不会检查溢出
- cargo build 默认 debug 模式 ,可以加 —release 切换到 release 模式
- 要显式处理溢出,可以适用标准库提供的
.overflowing_*
系列方法,比如:overflowing_add ```rust // 需要处理整数溢出 fn avg(a: u32, b: u32) -> u32 { // Hacker-sDelight 中的技巧 (a & b) + ((a ^ b) >> 1) }
fn main() { assert_eq!(avg(4294967295, 4294967295), 4294967295); assert_eq!(avg(0, 0), 0); assert_eq!(avg(10, 20), 15); assert_eq!(avg(4294967295, 1), 2147483648); println!(“passed”) }
<a name="qeZ0a"></a>
## 浮点数
- f32
- f64(未标注类型默认使用)
<a name="bVcJG"></a>
## 布尔值
- true/false,大小是一个字节
<a name="MnRyf"></a>
## 字符
- 单个字符,使用单引号包装
- `let c = 'z'`
<a name="PNtdq"></a>
# 复合类型
<a name="OIngO"></a>
## 元组
- 组合多个各种类型的值
- 有固定长度,一旦声明就不能修改长度
```rust
let mytuple: (i32, char) = (10, 'a');
// 解封装:
// 模式匹配 let (c, d) = mytuple;
// 索引访问 mytuple.0,mytuple.1
数组
- 一系列类型相同的数据
- rust 编译时对于数组的访问有越界检查 ```rust // [类型; 长度] let myarray: [u32; 5] = [1, 2, 3, 4, 5];
// 但是这样用index访问编译不会报错,会在运行时报错
let myarray: [i32; 5] = [1, 2, 3, 4, 5];
// unwrap 是 parse 失败时让程序崩溃
let index = “5”.parse::
<a name="i4XCc"></a>
## 切片
- 是对一个数组(包括固定大小数组和动态数组)的引用片段
- 有利于安全有效地访问数组的一部分,不需要copy数组中的内容
- 切片在编译时长度未知
- 底层实现
- 一个切片保留着两个usize成员
- 一个指向切片起始位置的指针,一个表示长度
```rust
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice = &arr[0..3]; // 取前3个元素
println!("{} {} {} {}", slice[0], slice[1], slice[2], slice.len());
结构体
- 是多种不同数据类型的组合
- 与元组类型类似,区别在于我们可以为每个成员命名
- 可以使用struct关键字创建三种类型的结构:
- 元组
- 经典C结构
- 无字段的单元结构 ```rust // 元组 struct Pair(i32, f32); let pair = Pair(1, 0.1); println!(“{}”, pair.0);
// 经典的 C 结构 struct Person { name: String, age: u8, } let mike = Person { name: String::from(“mike”), age: 20, }; println!(“{}”, mike.name);
// 无字段单元结构,在范型中较为常用 struct Unit;
// 可以使用派生属性让struct可打印(实现std::fmt::Display)
[derive(Debug)]
struct Person { name: String, age: u8, } let mike = Person { name: String::from(“mike”), age: 20, }; println!(“{:?}”, mike); // Person { name: “mike”, age: 20 }
- 结构体和对象的区别:
- 结构体是数据的结合
- 对象是数据+算法的结合
<a name="Uvvo9"></a>
## 枚举
- 使用enum关键字创建,包含了取值的所有可能情况
- 有多种不同形式的枚举写法:
- 无参数枚举
- 带枚举值的枚举
- * 带参数的枚举
- 枚举通常与 match(模式匹配) 一起使用
```rust
// 无参数枚举
enum Planet {
Mars,
Earth,
}
// 带枚举值的枚举
enum Color {
Red = 0xff0000,
Blue = 0x0000ff,
}
// 带参数的枚举
enum IpAddr {
IPv4(u8, u8, u8, u8),
IPv6(u8, u8, u8, u8, u8, u8),
}
fn main() {
let localhost: IpAddr = IpAddr::IPv4(127, 0, 0, 1);
// 模式匹配
match localhost {
IpAddr::IPv4(a, b, c, d) => {
println!("{} {} {} {}", a, b, c, d);
}
_ => {} // 任何非IPv4的类型走这条路
}
}
一些语法知识
注释类型
普通注释
//
/*
*/
文档注释
- 是一种markdown格式的注释
- 用于对文档中的代码生成文档
- 可以使用 cargo doc 生成 html 文档 ```rust //! 这是模块级别的文档注释, 一般用于模块文件的头部
/// 这是文档注释, 一般用于函数或结构体的说明, 置于说明对象的上方. struct Foo;
<a name="gZ7FF"></a>
## println的一些用法
- println! 用于将数据打印到标准输出, 且在数据末尾自动带上换行符. 在所有平台上, 换行符都是换行符(没有额外的回车符)
```rust
// 可以使用额外的位置参数.
println!("{0}{1}{0}", 4, 2);
// 使用命名参数.
println!("name={name} age={age}", name="jack", age=6);
// 可以在 `:` 后面指定特殊的格式.
println!("{} of {:b} people know binary, the other half don't", 1, 2);
// 可以按指定宽度来右对齐文本.
println!("{number:>width$}", number=1, width=6);
// 在数字左边补 0.下面语句输出 "000001".
println!("{number:>0width$}", number=1, width=6);
// println! 会检查使用到的参数数量是否正确.
println!("My name is {0}, {1} {0}", "Bond");
// 编译将会报错, 请补上漏掉的参数:"James"
类型转换
- Rust 是强类型语言,不支持隐式转换
Rust 为类型转换提供了几种不同的方法:as/transmute
as 语法
最基础的转换方法,通常用于整数、浮点数、字符数据之间的类型转换
fn main() {
let a: i8 = -10;
let b = a as u8;
println!("a = {}, b = {}", a, b); // a = -10, b = 246
}
数值转换的语义:
两个相同大小的整型之间转换是一个no-op(比如:i32 -> u32)
- 从一个大的整型到小的,会截断
- 从小的整型到大的,会:
- 如果源类型无符号会补0(zero-extend)
- 有符号会补符号 (sign-extend)
- 从一个浮点转整型,会向0舍入
- 从一个整型转浮会产生整型的浮点表示,如有必要会舍入(未指定舍入策略)
-
transmute
as 只允许安全的转换(例如会拒绝尝试将4个字节转换为u32)
let a = [0u8, 0u8, 0u8, 0u8];
// 这种非安全转化会报错
let b = a as u32;
但是我们知道u32在内存中为4个连续的u8,所以可以告诉编译器以另一种数据类型对待内存中的数据
- 除非清楚知道自己在干什么,不然不推荐使用transmute
- 使用transmute,需要将代码写入unsafa块中 ```rust use std::mem;
fn main() { unsafe { // 字节序,Rust 默认小端序 // 00000000 10000000 00000000 00000000 let a = [0u8, 1u8, 0u8, 0u8]; // b = 0b1_00000000 let b = mem::transmute::<[u8; 4], u32>(a); println!(“b = {}”, b); // 256 let c: u32 = mem::transmute(a); println!(“c = {}”, c); // 256 } } ```