https://doc.rust-lang.org/std/index.html
main.rs
fn main() {
variables_demo();
const_demo();
}
变量和可变性(Variables and Mutability)
fn variables_demo() {
println!("variables_demo");
let x = 5; //不可变变量 x
println!("This x is {x}");
//x = 6; //对不可变变量 x重新赋值
//println!("This is {x}");
}
cargo run
会出现以下提示信息
PS F:\project\LFC.Rust.Demo\variables> cargo run
Blocking waiting for file lock on package cache
Blocking waiting for file lock on Cargo.lock file
Compiling variables v0.1.0 (F:\project\LFC.Rust.Demo\variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src\main.rs:5:5
|
3 | let x =5;//不可变变量 x
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
4 | println!("This x is {x}");
5 | x=6;//对不可变变量 x重新赋值
| ^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error
PS F:\project\LFC.Rust.Demo\variables>
Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。
常量(constants)
fn const_demo() {
println!("const_demo");
VERSION: &'static str = "0.1.0";
println!("VERSION is:{}", VERSION);
}
:::warning
- 声明常量使用 const 关键字,并且 必须 注明值的类型
- 不允许对常量使用 mut。常量不光默认不能变,它总是不能变
-
隐藏(Shadowing)
rust可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 隐藏(Shadowing) 了。
fn shadowing_demo() {
//不可变变量 x
let x = 5;
println!("The value of x is: {x}");
//创建了一个新变量 x,获取初始值并加 1,隐藏了第一个x
let x = x + 1;
println!("The value of x + 1 is: {x}");
{
let x = x * 2;
println!("The value of x * 2 is: {x}");
}
println!("The value of x is: {x}");
}
:::warning 这个程序首先将 x 绑定到值 5 上。接着通过 let x = 创建了一个新变量 x,获取初始值并加 1,这样 x 的值就变成 6 了。然后,在使用花括号创建的内部作用域内,第三个 let 语句也隐藏了 x 并创建了一个新的变量,将之前的值乘以 2,x 得到的值是 12。当该作用域结束时,内部 shadowing 的作用域也结束了,x 又返回到 6。 :::
The value of x is: 5
The value of x + 1 is: 6
The value of x * 2 is: 12
The value of x is: 6
复合类型(Compound types)
复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
元组类型(tuple)
https://doc.rust-lang.org/std/primitive.tuple.html
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。fn tup_demo() {
println!();
let tup: (i32, i32, f32) = (11, 100, 1000.1);
println!("The value of x is: {}", tup.0);
//解构
println!();
let tup2 = (500, 6.4, 1);
let (x, y, z) = tup2;
println!("The value of x is: {x}");
println!("The value of y is: {y}");
println!("The value of z is: {z}");
//使用点号(.)后跟值的索引来直接访问
println!();
let tup3: (i32, f64, u8) = (500, 6.4, 1);
println!("The value of tup3.0 is: {}", tup3.0);
println!("The value of tup3.1 is: {}", tup3.1);
println!("The value of tup3.2 is: {}", tup3.2);
}
不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。
数组类型
https://doc.rust-lang.org/std/primitive.array.html
与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust中的数组长度是固定的。
创建数组有两种语法形式: 包含每个元素的列表,即 .[x, y, z]
重复表达式 ,它生成一个包含 副本的数组。的类型必须为“复制”。[x; N]Nxx
fn array_demo() {
println!();
//我们将数组的值写成在方括号内,用逗号分隔:
let arr = [1, 2, 3, 4, 24, 2, 14, 22];
println!("The value of arr first is: {}", arr[0]);
//方括号中指定初始值加分号再加元素个数[初始值; 元素个数]
println!();
let a = [3; 5];
for x in a {
print!("{x} ");
}
println!();
// 通过引用进行迭代
println!();
let array: [i32; 8] = arr;
for item in array.iter().enumerate() {
let (i, x): (usize, &i32) = item;
println!("array[{i}] = {x}");
}
}
函数(Functions)
Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。
什么是所有权?
Rust 的核心功能(之一)是 所有权(ownership)。虽然该功能很容易解释,但它对语言的其他部分有着深刻的影响。 :::warning
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
-
引用(reference)
引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用确保指向某个特定类型的有效值。下面是如何定义并使用一个(新的)calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权: ```rust fn reference_demo() { let str = String::from(“hello”); let len = calculate_length(&str);
println!(“The length of ‘{}’ is {}.”, s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
我们传递 &s1 给 calculate_length,同时在函数定义中,我们获取 &String 而不是 String。这些 & 符号就是 **引用**,它们允许你使用值但不获取其所有权。图 4-5 展示了一张示意图。<br />![](https://cdn.nlark.com/yuque/0/2022/svg/12561771/1661489383868-b5748693-728a-4e6a-a589-19121f01667d.svg#clientId=u9b78c57d-9c8b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=317&id=ueb3e5d6c&margin=%5Bobject%20Object%5D&originHeight=130&originWidth=300&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ua9038022-c01b-437b-8c49-9221c207b2e&title=&width=732)
:::warning
注意:与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符,*。我们将会在第八章遇到一些解引用运算符,并在第十五章详细讨论解引用。
:::
<a name="vALNZ"></a>
### 借用
我们将创建一个引用的行为称为 **借用**(_borrowing_)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。
```rust
//可变引用
fn reference_mut_demo() {
println!();
let mut str = String::from("hello");
change(&mut str);
println!("The str is '{}' ", str);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
悬垂指针
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
Slice 类型
slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。
字符串 slice
字符串 slice(string slice)是 String 中一部分值的引用,它看起来像这样:
//字符串 slice
fn string_slice_demo() {
println!();
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
}
hello 是一个部分 String 的引用,由一个额外的 [0..5] 部分指定。可以使用一个由中括号中的 [starting_index..ending_index] 指定的 range 创建一个 slice,其中 starting_index 是 slice 的第一个位置,ending_index 则是 slice 最后一个位置的后一个值。
结构体(struct)
struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。
结构体定义
定义结构体,需要使用 struct 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 字段(field)。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
结构体实例
fn create_user() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
}
类单元结构体
我们也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)因为它们类似于 (),即“元组类型”一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
方法(method)
方法(method)与函数类似:它们使用 fn 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文。并且它们第一个参数总是 self,它代表调用该方法的结构体实例。
定义方法
pub struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
impl User {
//定义方法
fn set_username(&self, username: String) -> Self {
self.username = username;
}
}
关联函数
所有在 impl 块中定义的函数被称为 关联函数(associated functions),因为它们与 impl 后面命名的类型相关。我们可以定义不以 self 为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例。
pub struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
//关联函数
impl User {
//定义方法
fn set_username(&self, username: String) -> Self {
self.username = username;
}
}
多个impl块
每个结构体都允许拥有多个 impl 块。例如,示例 5-16 中的代码等同于示例 5-15,但每个方法有其自己的 impl 块。
pub struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
//关联函数
impl User {
//定义方法
fn set_username(&self, username: String) -> Self {
self.username = username;
}
}
//关联函数
impl User {
fn get_username(&self) -> String {
self.username;
}
}
枚举(enumerations
枚举值
fn main() {
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
}
Option枚举
Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。
enum Option<T> {
None,
Some(T),
}
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
}
控制流结构(match)
Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。
可以把 match 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 match 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {}
模块(models)
模块是零个或多个项目的容器。
新创建一个models文件夹,添加相应的结构体文件。
pub struct User {
pub active: bool,
pub username: String,
pub email: String,
pub sign_in_count: u64,
}
impl User {
//定义方法
pub fn set_user(username: String) -> User {
User {
active: true,
username: username,
email: String::from("longfuchu@163.com"),
sign_in_count: 22,
}
}
}
Rust 会将一个 xxx.rs 文件默认为一个模块,但是普通的文件夹不能被 Rust 编译器识别。当文件夹下有 mod.rs 文件时,该文件夹才能被识别为模块。
此时模块 user与模块 models还没有直接关系,需要在 mod.rs 文件中引入 user。
pub mod user;
在main.rs将 models模块引入到 crate root 中
mod models;
let user1 = models::user::User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
mod models;
//结构体
fn create_user() {
println!();
let user1 = models::user::User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("The user1 is '{}' ", user1.email);
}
fn main() {
create_user();
}