Rust学习笔记(一)

参考文献1:https://course.rs/first-try/installation.html

参考文献2:rust-based-os-comp2024/2024-spring-scheduling-1.md at main · LearningOS/rust-based-os-comp2024 · GitHub

使用wsl2 ubuntu 20.04,照着教程直接

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

 然后无脑回车安装完成。

基础1 牛刀小试

cargo new <project-name> # 创建新项目(自动git)

cargo run # 直接编译运行,可添加--release生成高性能代码
cargo build # 先编译,可添加--release生成高性能代码
./target/debug/<project-name> # 运行可执行文件

cargo check # 在不编译的情况下快速检查能否编译通过【神器】,但比Go差太远

Cargo.toml:项目描述,跟package.xml差不多。

依赖直接以如下形式给出:

[dependencies]
rand = "0.3" # 官方包,只需指定版本
hammer = { version = "0.5.0"} # 指定版本的另一种方法
color = { git = "https://github.com/bjz/color-rs" } # 网上的包,指定url
geometry = { path = "crates/geometry" } # 本地的包,指定文件路径

Cargo.lock:根据toml生成的项目以来详细清单,基本不需要手动修改。

基础2 Rust入门(乱序笔记,建议跳过)

let绑定的变量不可变,let mut的可变,并且可以一个let赋多个值。

我秉承实用主义解释方法,rust的话术后面深入了解之后再接受。

let {mut} <var-name>: <type>{ = <value>};
let ({mut} <var>, {mut} <var>, ...): (<type>, <type>, ...){ = (<value>, <value>, ...)};

这多个值实际上构成了一个复杂变量,但可以单独使用。这种随地凑出向量的搞法比较优雅。

下划线开头的变量不会因为未使用而被警告。

定义成mut但没变过的变量会被警告。

非常古典的类型名,i32、f64这种。

后定义的同名变量会把之前的覆盖,且不会报警。

数字可以在后面加上下划线(也可以不加)+类型规定为对应类型的数字。

序列用法:

1..5 // 代表1~4
1..=5 // 代表1~5
// 字符也可以用
fn func(var: type, ...) {
    let x = {
        let y = 3;
        y * 2
    };
    x + 3
}

不加分号的表达式会返回一个值,可以作为函数返回值,也可以作为语块表达式返回值。

if-else语块也可以返回值,可以用来赋值。

Rust也可以用return,只不过可以不用。

let x = 5;
let y = x; // 直接拷贝值

let x = String::from("Hello"); // 堆上分配内存,创建String对象
let y = x; // 把x绑定的值的所有权移交给y,x被废黜

let x: &str = "Hello"; // 字符串字面量只读,放在静态数据区域,此处的x只是一个引用
let y = x; // 相当于x是一个指针,然后指针值拷贝给了x

基本类型注意:

let a = [1, 1, 4, 5, 1, 4]; // 用.len()获取数组长度

let b = &a[2..4]; // 切片以引用的方式获取,注意左开右闭。左右界限均可以省略。

let c = (1, 1, 4, 5); // 用c.2获取第3个数

let v = vec![10, 20, 30, 40]; // 宏定义向量

for e in v.iter_mut() { // 要允许值可变,要用iter_mut()
    *e *= 2; // 用的是引用值,要用*解引用?
}

v.iter().map(|element| { // 对迭代器中每个元素应用一个匿名函数
    element << 1
}).collect() // 将迭代器的结果收集起来组成新的集合
fn main() {
    let vec0 = Vec::new();

    let mut vec1 = fill_vec(vec0);

    println!("{} has length {}, with contents: `{:?}`", "vec0", vec0.len(), vec0);

    vec1.push(88);

    println!("{} has length {}, with contents `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    // 这里把vec0的数据借调了,所以vec0访问不了。所以上面的fill_vec得是深拷贝。
    let mut vec = vec;

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

结构体的clone方法需要额外#[derive(Clone)]才能使用。

用impl <结构体名>来定义方法。

需要用&的情景:

  1. 借用数据,实际上相当于取地址。要使用数据(比如算术运算)时需要加*解引用,但是rust居然会自己解引用。感觉这个设计非常nt,落了下乘。也许我后面会理解的吧。
  2. 避免数据复制(传递复杂数据结构的引用)
  3. 共享不可变数据
  4. 可变引用&mut:每个数据同一时间只能有一个&mut存在,而且被引用的必须是mut变量。
  5. 参数中用&可以避免拥有数据所有权,特别是在只需要读取的时候。其实还是传地址,只是rust会自己解引用。

rust的枚举类型可以定义:

  1. 结构体变体,即不加struct前缀的三种结构体
  2. 方法,跟struct类似,要用impl在外面定义
  3. 用Nested(<其他枚举类型>)进行嵌套枚举

match的标准用法:

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("Quit message"),
        Message::ChangeColor(r, g, b) => {
            println!("Change color to RGB({}, {}, {})", r, g, b);
        }
        Message::Move { x, y } => {
            println!("Move to ({}, {})", x, y);
        }
    }
}
let a = "test";
let b = a.to_string(); // 字符串字面量转String

let a = String::from("test");
let b = &a; // String转字符串字面量:直接引用地址

不允许let mut (l, r) = (0, 0);,非常抽象。

调用成员方法的时候会自动解引用,与是否是函数参数无关。&会直接作用于调用过方法后的整体。

字符串操作:

let s = String::from("test");

// 遍历:直接得到字符值
for c in s.chars() {
    if c == ' ' {
        break;
    }
}

s.replace(<要替换的字符串>, <要换成的字符串>);
s.replacen(<要替换的字符串>, <要换成的字符串>, <替换数目>);
// 只要有一个String类型,其他的即使是&str也可以相加

不加pub全是隐藏,包括use。

序列要遍历需要.iter(),但hashmap遍历不需要,自身就是一个集合。

遇到任何不需要转移值的地方都要打&。

Some()把一个变量变成optional变量。

if let和match都是模式匹配的方法。

如果被匹配的option变量在模式匹配后还要使用,那么不能够使用Some(v),而是要使用Some(ref v),避免所有权被夺走。

抛出错误用Err(String),不抛出用Ok(String),返回值都是Result<成功时返回的类型,错误时返回的类型>。

parse自己返回值就是Result类型。

?很厉害,对于返回Result的,如果Err就立刻返回Err,如果有值就取出值,继续执行;对于返回Option的,如果None就立刻返回None,如果有值就解出Some(v)的v,继续执行。

但使用?的函数必须是Result返回值,包括main本身。在非错误情形下,要调用Ok(())来返回一个空单元。

?遇到错误或None会立刻返回,但Err并不会,后续的代码会继续执行。

注意:只有函数结束时的表达式才能作为返回值。

用Result<type, Box<dyn std::error::Error>>装各种错误。

泛型写法:

  1. struct泛型直接在名字后面跟<>
  2. impl<T> Struct<T> {}里面的函数名字后面不用跟<>,调用的时候Struct::method()完全不用写<T>,大概是都可以自己推断?

trait感觉像C++的虚函数,声明但不实现。trait xxx由后面的impl xxx for type来实现。

但是trait似乎是任何对象都可以通用的,作为公共的.xxx()接口。

self是当前对象,Self是当前类型。

trait的默认函数跟C++虚函数一样,可以在声明虚函数处定义默认操作。

若只关心对象是否有特定trait,只需把对象类型写成impl <trait名称>即可。比较灵活。

要是关心对象是否有多个特定trait,只需一个impl,后面把trait名称用+连起来即可。

用#[should_panic(expected = "...")]捕获panic信息,使得panic不显示。即“预料之中的panic”。

unsafe操作:用unsafe <不安全函数定义>或者unsafe{<不安全运算/函数调用>},一般把&mut用as转成*mut使用。例程:

unsafe fn modify_by_address(address: usize) {
    unsafe {
        let ptr = address as *mut u32;
        *ptr = 0xAABBCCDD;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_success() {
        let mut t: u32 = 0x12345678;
        unsafe { modify_by_address(&mut t as *mut u32 as usize) };
        assert!(t == 0xAABBCCDD);
    }
}

也可以用::into_raw()生成裸指针,然后用::from_raw()解引用。不过这个应该需要derive?

#[cfg(<逻辑表达式>)] 实际上就是条件编译。

cargo:rustc-env=<环境变量名>=<值> # 定义环境变量
cargo:rustc-cfg=<属性名>{=<值>} # 相当于define,值可以省略

extern引入的函数默认是unsafe的,

extern "<ABI类型>" {
    <Rust下的函数声明>;
    ...
}

相关的函数attribute

#[no_mangle] // 不让外部函数覆盖本地函数名称
#[link_name = "xxx"] // 让这个外部函数在本地可以用xxx的名字被调用

生命周期标记:'<字母和数字的组合,或是下划线>。一般来说'a、'b这一类就够用了。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str // 让函数参数和返回值的生命周期一致
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str // 让返回值和第一个参数的生命周期一致

rust里的花括号不能乱加,因为作用域和生命周期强绑定。

对于形如

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is '{}'", result);
}

造成的生命周期不统一问题只能靠移动string2或者println!来实现,非常抽象。

struct Book<'a> {
           ++++
    author: &'a str,
             ++
    title: &'a str,
            ++
}

fn main() {
    let name = String::from("Jill Smith");
    let title = String::from("Fish Flying");
    let book = Book { author: &name, title: &title };
    // 此后未再用过name和title,于是name和title的符号被连同数据一起销毁
    // 然而它们的数据还会被book使用,所以要让author和title的生命周期跟book一样长
    // 即,author和title借来的数据不会在name和title的销毁时失去
    println!("{} by {}", book.title, book.author);
}

一个典型的vec操作:

fn main() {
    let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];

    let mut my_iterable_fav_fruits = my_fav_fruits.iter();   // TODO: Step 1

    assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple"));     // TODO: Step 2
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach"));     // TODO: Step 3
    assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
    assert_eq!(my_iterable_fav_fruits.next(), None);     // TODO: Step 4
}

需要注意的是,一个.iter()得到的迭代器是空头的,即,要再next()一下才得到第一个元素。

沙比String只能+= &str,不能加String。感觉跟shell语言有相似之处,等号右侧使用的实际上是$var而不是var。

enum里的结构体要用<enum名>::<结构体名> {构造}来引用,而不是<enum名>(<结构体名> { .. })。

注意以下区别:

for ref x in set.iter() = for x in set.iter()
for mut x in set.iter() // 这种写法没用
for x in set.iter_mut() // 这才是正确的

这里面的x是迭代器,即&<set元素类型>,直接调用x.method()会自动解引用,但调用函数得用func(*x)。

不用for/while,纯用迭代器遍历有以下几种方法:

let mut vec = Vec::new();
let mut map = HashMap::new();


// for_each,此处写两种,以下只写vec
vec.iter().for_each(|element| {
    <执行操作>
});
map.iter().for_each(|(key, value)| {
    <执行操作>
});

vec.iter().map(|element| {
    <执行操作>
    element
}).collect(); // 执行体返回element的话相当于消耗了vec之后又得到一个vec

vec.iter().inspect(|&element| {
    <执行操作>
}).count(); // 执行count来消费迭代器

// 对于字符串是string.chars()而不是iter(),后面差不多

最好用的还是for_each。

thread::spawn(|| {})借用外部变量,thread::spawn(move || {})获取外部变量所有权。

spawn返回handle,handle.join()跟踪线程,并获取Result形式的返回值。

let a = Arc::new(value); // 创建线程安全的共享变量
let b = Arc::new(Mutex::new(value)); // 创建线程安全的共享互斥变量
let c = a.lock().unwrap(); // 互斥变量只有lock了才能确定,共享变量必须unwrap

for x in xxx.iter()和for x in &xxx是等效的,不过传出来的东西有点区别。 

定义结构体的时候可以类型名挨着<xxx>,但是调用的时候中间全都得加上::。

几种智能指针:

let a = <值>;
let shared_a = Arc::new(a); // 生成线程安全变量,并夺权
let thread_a = Arc::clone(&shared_a); // 对每个线程生成单独的变量
// 一般来说,只有.clone()和传引用可以不夺权,其余的都会夺权。

// Box = unique_ptr in C++
enum List {
    Cons(i32, Box<List>),
    Nil
} // Box可以允许嵌套定义

let a = Box::new(<值>);
println!("{}", *a); // 相当于生成值对应的指针,并且会自动释放

// Box相当于是Rust的常用指针
// Box<List>用于定义或声明,Box::<List>::..用于调用

let a = Cow::new(<变量>); // 直接夺权
let b = Cow::new(<引用>); // 借用,如果经由b想要修改数据,则夺权

// Rc = shared_ptr in C++
let a = Rc::new(<变量>); // 创建共享变量
let b = Rc::clone(&a); // 指向同一个变量
println!("{}", Rc::strong_count(&a)); // 输出引用计数

宏定义:

macro_rules! my_macro {
    () => {
        println!("Check out my macro!");
    }; // 不同分支之间要用分号隔开
    ($val:expr) => {
        println!("Look at this other macro: {}", $val);
    }
}

可以定义在module里,宏上方写#[macro_export],然后在外面直接用my_macro!()调用。

Clippy很强,多用。

as可以用来转换类型,也可以用来改变引进的包名。

写类型转换:

use std::convert::{TryFrom, TryInto};

impl TryFrom<源类型> for 目标类型 {
    fn try_from(参数: 源类型) -> Result<Self, Self::错误类型> {
        ...
    }
}

let a = 目标类型::try_from(源类型对象);

let b: Result<目标类型, _> = 源类型对象.try_into(); // 允许失败的尝试类型转换,可以不写impl

对于泛型函数,用<T: AsRef<具体类型>>来在函数中使用as_ref(),用<T: AsMut<具体类型>>来在函数中使用as_mut()。这两种方法同时限制了泛型类型,因为例如AsRef<str>只有str类型有。

From/Into只需要定义其一就够了,TryFrom/TryInto同理。只是From/Into不会处理出错情况。

杂乱无章的知识学习结束了,接下来是算法部分。

基础3 算法实现

其实主要是数据结构,大部分算法所有语言都差不多。

冒泡排序:

fn sort<T: std::cmp::PartialOrd + Clone>(array: &mut [T]){
	//TODO
    let n = array.len();
    for i in 0..n {
        for j in i + 1..n {
            if array[i] > array[j] {
                // 单纯的变量交换和数组元素交换略有不同,前者第三步可以靠重新绑定tmp的值来
                // 完成,不需要克隆两次,但array[j]不能let重绑定。
                let tmp = array[i].clone();
                array[i] = array[j].clone();
                array[j] = tmp;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值