本文源自观看B站Rust编程语言入门教程记录下来的重点笔记
视频链接:Rust编程语言入门教程(Rust语言/Rust权威指南配套)
shadow:定义了一个变量后,用let再给同名变量赋值即为shadow,可改变原值(原值不一样的类型也可以)。
如果用let mut定义了一个变量类型后,这个变量不能赋值给不同类型。
rust是静态编译语言,在编译时必须知道所有变量的类型。有时可能的类型比较多,编译器无法自动推断,就必须添加类型标注,否则编译会报错(如把String转为整数的parse方法)
标量类型:整数、浮点、布尔、字符
复合类型:
可以将多个值放在一个类型里,rust提供了两种基础的复合类型:tuple和数组。
Tuple:
let tup(i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; println!(tup.1);
数组:
let a:[i32; 5] = [1, 2, 3, 4, 5]
或 let a = [3(初始值); 5(元素个数)];
声明使用fn关键字,针对函数和变量名rust都适用snake case命名规范:所有字母都是小写,单词之间使用下划线分开。并且不像C++函数生命位置与调用顺序无关(没有前置声明)
if,else
循环:
for i in elements.iter()
类似于迭代器,更安全且简洁for number in (1..4).rev() {}
输出321所有权是rust最独特的特性,让rust无需垃圾回收器(GC)就可以保证内存安全。
Statck vs Heap:
在像rust这样的系统级编程语言下,一个值在stack还是在heap上有重大影响;在代码运行时这两者都是你可用的内存,但它们的结构很不相同。
Stack按值的接受顺序来存储数据(压入栈),按相反的顺序将它们移除(弹出栈)(后进先出)
所有存储在栈上的数据都必须拥有已知的固定的大小
把值压到栈上不叫分配
因为指针是固定大小的所以可以存放在栈上
Heap内存组织差一些,当把数据放入heap时会请求一定数量的空间
操作系统在堆中找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址;这个过程叫做在heap上进行分配
把数据压到栈上要比在heap上分配快得多:因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在栈的顶端
然而在heap上分配空间需要做更多的工作:操作系统首先找到一块儿足够大的空间存放数据,然后要做好记录方便下次分配
访问数据:
函数调用:
当代码调用函数时,值被传入到函数(包括指向堆区的指针);函数本地的变量被压入到栈上,当函数结束后,这些值会从stack上弹出
所有权存在的原因:
解决存放在堆中数据的内存浪费问题;加入一个结构体中存放了一个字符串类型(动态数据类型,在堆上分配)和一个整形(固定类型大小,在栈上分配),如果多次复制该结构体,会造成结构体中的字符串在堆上分配多次造成内存空间浪费,这时rust凭借所有权可以将字符串字段的所有权转移给结构体的所有者,当所有者离开作用域时,字符串字段的内存将被自动释放,从而避免重复占用堆上的内存。 ex:let s = String::from("hello"); let p = Person { name: s, age: 30 };
在这个例子中,一个字符串值"hello"被动态分配在堆上,并将其所有权赋值给变量s。然后,将s的值赋值给Person结构体的name字段,这意味着字符串的所有权被转移给了Person结构体变量p。当p变量离开作用域时,字符串的内存将被自动释放。
所有权规则:
String类型:
let s = String::from("Hello");
内存与分配:
rust中对某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给系统(内存释放);即自动调用drop
函数
变量和数据交互的方式:
Stack上的数据:复制(Copy)
let x = 5;
let y = x;
println!("adda {:?}", x); // Output:5
// 这段代码中的变量 x 拥有整数值 5 的所有权,然后将其赋值给变量 y。由于 i32 类型是 Copy trait 的实现类型,因此这个赋值操作不会移交整数值的所有权,而是复制一份新的值给 y,因此 x 和 y 都拥有整数值 5 的所有权。
在语义上将值传递给函数跟把值赋给变量是类似的:将发生移动或复制(取决于不同的变量类型)
把引用&作为函数参数的行为叫做借用
可变引用(&mut):
悬空引用:
字符串切片:
&s[..]
&str
类型的变量,它是对原始字符串的一个不可变引用。因为它是一个引用类型,所以它本身不拥有所引用的数据的所有权,而是借用了原始字符串的所有权。同时,由于它是不可变引用类型,所以它不能修改原始字符串中的数据因为简单的类型变量自带Display这个trait所以用{}可以直接输出,但是像struct这种类型则需要加上#[derive(Debug)]
来显示打印调试的功能,这样就可以使用{:#?}
来格式化输出
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32{
self.width * self.length
}
}
String::from()
每个struct允许拥有多个impl块
枚举允许我们列举所有可能的值来定义一个类型
// 定义枚举
enum IpAddr{
V4,
V6,
}
// 枚举值
let four = IpAddr::V4;
// 枚举的变体都位于标识符的命名空间下,使用两个冒号::进行分隔
允许数据附加到枚举的变体中:
优点:
ex:
enum IpAddr{
V4(u8, u8, u8, u8), // 无论是数字、字符串、结构体还是枚举类型都能嵌入
V6(String),
}
为枚举定义方法:也使用impl关键字
定义与标准库中,在预导入模块中,描述了某个值可能存在(某种类型)或不存在的情况
Rust没有Null但是它提供了一个类似Null概念的枚举 - Option
标准库中的定义:
enum Option<T> {
Some(T),
None,
}
// 这三者都包含在预导入模块中,均可直接使用
这样Option
比Null好在哪儿呢?
Option
与T是不同的类型,不能把Option
直接当成T来用(比如相加减)强大的控制流运算符,允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码;模式可以是字面值、变量名、通配符…
ex:
enum Car{
laosilaisi,
benchi,
aodi,
hamadi,
}
fn trans(car: Car) -> String {
// 必须要穷举所有的模式
match car {
Car::laosilaisi => String::from("劳斯莱斯"),
Car::benchi => String::from("奔驰"),
Car::aodi => String::from("奥迪"),
// Car::hamadi => String::from("哈马迪"),
_ => (),// 但使用_通配符可以替代其余没列出来的值
}
}
fn main() {
let c:Car = Car::aodi;
let x = trans(c);
println!("{}", x);
}
ex:
if let Car::aodi = car {
String::from("奥迪")
} else {
String::from("其他车")
}
私有边界:
ex:
mod front_of_house {
// 注意以下模块和方法如果不加pub在下方调用时都会报错
pub mod hosting {
pub fn add_to_house() {}
}
}
pub fn eat_at_res() {
crate::front_of_house::hosting::add_to_house(); // 绝对路径
front_of_house::hosting::add_to_house(); // 相对路径
}
super关键字:
比如上面的例子:
pub fn add_to_house() {
// 因为上级有两个mod所以要使用两次super
super::super::eat_at_res(); // 相对路径
crate::eat_at_res(); // 绝对路径
}
pub struct:
pub enum:
use的习惯用法:
as
创建别名将模块内容移动到其他文件:
创建Vector:
let v: V<i32> = Vec::new();
let v = vec![1, 2, 3]; // 初始值创建方式用vec!宏
// 即使初始化时没有指明存储类型,rust可以上下文推断出类型
let mut v = Vec::new();
// 更新vector
v.push(3);
v.push(4);
读取Vector中的元素:
&v[0]
所有权和借用规则
let v = vec![1, 2, 3];
let first = &v[0];
v.push(6); // 报错
// 试想一下vector工作原理,vector是动态分配的连续容器,如果push之后容器找了一块扩充的新的内存地址,而first还指向原内存地址,那么就会发生内存泄漏(所以借用规则防止该情况发生)
vector遍历
ex:
let v = vec![1, 2, 3];
// 注意是引用,如果要修改v里的值就用&mut,然后用解引用符号*
for i in &v{
println!("{}", i);
}
println!("{:?}", v);
vector还可以与枚举enum配合使用
更新String:
push_str()
方法:把一个字符串切片附加到Stringpush
方法:把单个字符附加到String+
:连接字符串;相当于add(self, s: &str)format!
:连接多个字符串
println!
类似,但返回字符串Rust不允许对String进行索引
切割String:
[]
和一个范围来创建字符串的切片
遍历String
chars()
方法bytes()
方法HashMap和所有权:
更新HashMap:
or_insert()
方法插入enum Result<T, E>{
Ok(T), // T:操作成功时Ok变体里返回的数据类型
Err(E), // E:操作失败时Err变体里返回的数据类型
}
处理Result的一种方式:match表达式
和Option一样,Result及其变体也是由prelude带入作用域的
ex:
use std::{fs::File};
fn main() {
let file = File::open("hello.txt");
let _f = match file {
Ok(file) => file,
Err(e) => {
panic!("Error opening file {:?}", e)
}
};
}
unwrap:match表达式一个快捷方法
?运算符:
传播错误的一种快捷方式
?与from函数:
用于针对不同的错误原因返回同一种错误类型,只要每个错误类型实现了转换为所返回的错误类型的from函数
?与main函数:
函数定义中的泛型
Struct定义中的泛型
#[derive(Debug)]
struct Car<T, U> {
door: T,
people: U,
}
fn main() {
let x = Car {door:3, people: "sdad"};
println!("{:#?}", x);
}
Enum定义中的类型
Option, Result
方法定义中的泛型
impl Point
impl Point
泛型代码的性能:
格式
pub trait Summary{
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub baseline: String,
}
impl Summary for NewsArticle{
fn summarize(&self) -> String {
format!("{}, {}", self.headline, self.baseline)
}
}
Trait作为参数