rust学习笔记

2022年 6月 5日 29点热度 0人点赞

file

项目管理

cargo 用于管理项目.

cargo new xxx     # 创建一个 xxx 的项目
cargo build       # 编译并构建出可执行文件
cargo update      # 更新
cargo run         # 构建并运行
cargo check       # 检查是否有语法错误

变量可以被 shadow, 也就是屏蔽之前的变量, 弄一个新的变量

let mut a, 变量默认是 immutable 的, 如果用了 mut 修饰则是 mutable 的.

.toml 这个文件用于管理依赖和依赖的版本.

let guess: u32, 标识你想得到一个无符号的 32 位的整数.

for 循环用的是 loop, break 可以跳出循环

    loop {
        match guess.cmp(&secret_number) {
            Ordering::Less => println! ("Too small!"),
            Ordering::Greater => println! ("Too big!"),
            Ordering::Equal => {
                println! ("You win");
                break;
            }
        }
    }

分支匹配还可以用 match:

match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
}

返回的结果是一个枚举, Result

常量

const THREE_HOURS_IN_SECOND: u32 = 60 * 60 * 3;

Rust 中的数据在运行时会有越界检查.

Rust 是一门静态类型的语言, 嗯, 我就喜欢静态的, 不喜欢动态的.

Rust 中的函数定义和调用之间没有先后顺序, 只要你在某个位置定义了就行.

函数的参数定义为: 形参名: 类型

他妈的, 为参数名和参数类型之间要有分号, 这他喵的不是多打字吗?

Rust 中的 statement 和 expression 并不是一个东西, statement 没有返回值, expression 有返回值的. expression 可以是 statement 的一部分.

调用宏也是一个 expression, 花括号包裹的域也是一个 expression.

    let y = {
        let x = 3;
        // 卧槽, 这个地方还不能用分号
        x + 1
    };

表达式并不包括结尾的分号.

表达式加了分号就变成了 statement.

Rust 这个 if 怎么和 go 挺像的?

Rust 的 if 只会执行第一个条件为 true 的 block, 后面的其他即使为 true 也不再执行了.

突然感觉 match 和 switch 还挺像的?

let number = if condition { 5 } else { 6 };

不太喜这种形式, 还是喜欢三元表达式, 另外这里的 if 和 else 的类型应该一致.

为啥要一致, 是为了让 Rust 在后续的编译时可以做类型校验, 如果 Rust 不知道的话就做不了类型校验了.

可以在 loop 前指定标签 (字符串, 'counting_up: loop {}), 然后 break label, 就可以直接跳出来了.

loop 还可以作为返回值.

ownership

好像到了最精彩的环节了.

  • ownership
  • borrowing
  • slices
  • memory layout

所有权是 Rust 管理内存的一套规则. Rust 使用了一套编译器可以检查的所有权系统的规则来管理内存. 如果违反了任意一项规则, 那么程序将不会被编译. 并且所有权的这个功能不会拖慢系统的运行.

因为所有权对于很多的程序员来说是一套全新的规则, 所以确实要花时间来适应. 如果你越来越适应 Rust, 那么你会很自然地开发出安全且高效的代码.

当你你理解了所有权, 你将对 Rust 的这个特有的属性有更加坚实的认识. 这里将通过 strings 的例子带你学习 Rust 的所有权.

堆和栈

push 到 stack 要比 heap 要快, 因为分配器不用去寻找可用的空间, 数据总是存在栈顶.

所有权规则

  • 在 Rust 中的每个值都有一个变量, 被称为所有者 (owner)
  • 同一时间只有一个所有者
  • 当所有者离开了域, 这个值将会被 drop 掉

变量的域

当一个变量离开了作用域, Rust 将会调用一个特殊的函数, 这个函数被称之为 drop.

在变量的生命周期结束, C++ 也会释放资源, 这被称之为 RAII (Resource Acquisition Is Initialization).

这种模式对于 编写 Rust 代码而言有着深远的影响.

move

let s1 = String::from("hello");
let s2 = s1;

println! ("{}, world!", s1);

这他妈的是不是有点激进了, 直接 s1 就无效了, 也就是从 s1 move 到了 s2.

Rust 永远也不会对你的数据进行深拷贝. 因此运行时的这种拷贝几乎没有开销.

也可以深拷贝:

let s1 = String::from("hello");
let s2 = s1.clone();

好像有点李姐了所有权, 就是谁用谁负责.

赋值, 函数调用传参, 或者返回值都会转移所有权.

如果是函数调用传参的时候需要交出所有权, 函数调用完成了又要归还所有权, 但是如何实现函数调用的时候所有权还是在调用者那边呢?

References and Borrowing

引用和指针类似, 不同的是, 引用确保是指向了一个特定类型的有效的值.

let s1 = String::from("hello");
let len = calculate_length(&s1);

&s1 可以创建 s1 的引用, 但是并不拥有 s1, 所以引用不再使用的时候并不会调用 s1 的 drop.

这样的用法被称之为 reference borrowing.

举个例子, 就像现实生活中, 有一个人拥有一样东西, 你可以从他那儿借用, 但是你用完之后必须还给别人.

你借用的东西只能使用, 不能修改搞破坏.

就像变量默认是 immutable, 引用默认也是 immutable.

另外一种就是可修改的引用 (mutable reference).


fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

mutable reference 的使用有一个限制就是, 在同一段代码中, 只能有唯一的一个可变引用.

也就是, 我们的同一个东西不能借给别人两次.

我试了下 immutale reference 可以被借出去多次.

感觉和一般的编程语言不同的是, rust 给程序员施加了很多的限制, 不让你瞎几把写代码, 你只能按照我的规矩来写. C++和 C 就灵活许多, 但是这种灵活性未必不会对程序员带来负担. Java 就是在 C++ 的基础上做的减法.

感觉 rust 这种设计就是一种折中吧, 想要安全和内存管理的好处就要遵守我的规则, 算是在无 GC 和有 GC 开辟了第三条路吧, 挺好的, 我决定学学.

只能以 mutable reference 的方式借出一次的好处是 Rust 可以避免在编译期的数据竞争.

数据竞争 (data race) 和竞态条件类似:

  • 同一时间有两个指针指向了同一片数据
  • 至少有一个指针会修改数据
  • 对于数据的访问没有任何同步机制

想要多个可变引用的方法就是, 我们可以通过大括号来创建多个域.

同样的, 不能在同一个域, 对同一个数据既有可变引用也同时有不可变引用.

但是如果先使用不可变引用, 后使用可变引用, 如果不重叠的话, 就没得问题.

编译器能告诉引用不再使用的位置的这种能力被称为 Non-Lexical-Lifetimes(NLL), 详情见这里.

尽管 borrowing error 可能会出现很多次, 但是这个是 Rust 在提示里这里可能会出现 bug.

悬挂指针 (Dangling References)

Rust 向您保证: 如果你引用了某个数据, 编译器将会确保数据不会先于引用脱离作用域滴.(好奇, 这个是如何保证的?)

这里搞个例子来演示编译器如何处理悬挂指针的:


fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

这里报错会提示到一个功能点: lifetimes, 这个得等到第 10 章才讲.

由于 s 是在函数 dangle 中创建的, 所以在 dangle 结束之前, s 将会被 deallocated, 如果我们还返回这个值, 这意味着引用将会指向一个不存在的值, Rust 不允许我等这么干.

这个解决方法就是, 直接返回 String, 这样就会把所有权转移 (move) 出去了, 哇偶, 总之你调用我就转移给了你了, 下面的就是你负责了, 我就不管了. 所以调用函数还是得看函数的返回值.

引用的规则

  • 在任何给定的时刻, 你要么只有一个可变的引用, 要么可以有一个或者多个不可变的引用
  • 引用必须永远是有效的

下面会介绍另一个引用: slices.

slice 类型

slice 可以让你以集合的方式引用连续序列的元素, 而不是整个集合本身. slice 是引用的一种, 所以它并不具有所有权.

file

rainbow

这个人很懒,什么都没留下

文章评论