Rust 100 黑
不定期更新
所谓爱到深处自然黑, 本文将会收集 Rust 的黑点, 并附上简单的解释和状态跟踪
至于能不能真的收集到 100 个, 希望不能吧
友情链接: https://3442853561.gitbooks.io/wtfrust/content/
-
enum
的内存占用尽管某些特殊类型会优化, 但大部分需要额外保存 tag, 而这个 tag 是 1byte, 极端情况下是个灾难
-
尾递归优化并不总是生效
当进行尾递归调用的作用域内有
Drop
变量存在时fn recur() { let x = vec![false]; recur() }
-
&mut
不是Copy
的,但却会被特殊处理fn drop_ref<T>(_: &mut T) {} fn drop<T>(_: T) {} fn assert_copy<T: Copy>(_: T) {} let x = &mut 1; // assert_copy(x); drop_ref(x); println!("{}", x); drop(x); // println!("{}", x);
试着去掉注释, Playground
-
'static
并不static
'static
甚至不是一个生命期参数 (它是一个特殊的 "字面量", 并作为一个关键字实现),在很多情况下它只被用来表示变量不受某个生命期约束很多人认为所有
'static
的变量会永远留在内存中,这是不正确的,一个 owned 类型也是'static
的fn assert_static<T: 'static>(_: T) {} assert_static(String::new());
-
没有泛型特化
要想特化只能重新包一层类型,不过快要解决了 #31844
-
Range overflowfor _ in 0..256_u8 { unreachable!(); }
Playground, 当然这有个警告, 以及还有 #28237
更新: 现在警告变错误, #28237 也实现了, 这条可以划掉了
-
易混淆的花括号
false && { true } && false
会被解释成
(false && { true } && false)
而
{ true } && false
会被解释成
{ true }; && false
-
Shadow 常量时, 糟糕的错误信息
const x: bool = false; let x = 1;
error[E0308]: mismatched types --> src/main.rs:3:9 | 3 | let x = 1; | ^ expected integral variable, found bool | = note: expected type `{integer}` found type `bool`
类似的还有 Unit Struct
struct x; let x = 1;
Playground, 当然只要遵守命名规范, 就不会遇到这种问题 (然而一个例子是 Diesel)
-
特殊情况下数字类型的类型推导
Rust 编译器对数字运算的类型推导有特殊处理, 以方便确定具体的长度和有无符号
然而这些规则只针对特定运算符, 并且各个运算符的规则都不同, 在不了解的情况下会导致混乱, 比如下面这行代码可以编译
(1u32 >> 2) | 3;
而这个理应等价的不能:
std::ops::Shr::shr(1u32, 2) | 3;
2 | std::ops::Shr::shr(1u32, 2) | 3; | ^ no implementation for `u32 | i32`
原因是编译器会默认
>>
的返回值和左手边的类型一样, 推导返回值类型时不需要依赖2
的类型而
shr
这个函数的返回值类型依赖参数的类型, 只能在2
推导完成后再推导, 这导致在同一个步骤中进行推导的3
无法确定类型, 就成为默认的i32
给
2
加上类型标注 (2u32
) 使返回值类型提前推导出来可以解决问题 Playground -
Float 的转换目前, float 对目标类型不能表示的值的转换是未定义行为
比如
NaN
和Inf
转换为 integer, 以及大于或小于f32
所能表示值的f64
转换为f32
更新: 不再是未定义行为了,这条划掉
-
闭包的捕获粒度struct Config { value: Option<String>, fallback: String, } impl Config { // 获取 value 的值, 如果 value 为 None, 自动将 value 的值设为 fallback // 不要吐槽为啥不直接返回 fallback, 这只是个例子 fn get_value1(&mut self) -> &mut String { // 这样可以工作, 但是如果 value 不为 None, 会做一次无意义的 Clone self.value.get_or_insert(self.fallback.clone()) } // 换一种实现方法 fn get_value2(&mut self) -> &mut String { // 所以我们写成这样, 然而问题来了, 会提示不能再次借用 self 因为已经借用了 self.value // self.value.get_or_insert_with(|| self.fallback.clone()) // 解决方法是不要让闭包捕获到整个 self let fallback = &self.fallback; self.value.get_or_insert_with(|| fallback.clone()) } }
Playground 和 相关文章 以及 RFC#2229
更新: Rust 2021 Edition 已默认启用 disjoint capture
-
智能指针的
DerefMove
Box 实现了
Deref
impl<T: ?Sized> Deref for Box<T> { type Target = T; fn deref(&self) -> &T { // ... } }
所以
let x: bool = *Box::new(false);
成立, 并被翻译为let x: bool = *Deref::deref(&Box::new(false));
当
T
为Copy
时这一点问题都没有, 但假如不是呢?deref
接受的是&self
, 按理说无法将T
move 出去但下面代码是可以运行的
let x: String = *Box::new(String::new());
原因当然是编译器对
Box
做了特殊处理而为了让其他智能指针也能做到, 有人提出了
DerefMut
, 但这是 Rust 1.0 发布之前的事, 拖到现在还没解决(或者说都没这需求)最近又有人提出了新的 RFC#2439, 拭目以待吧
-
智能指针的模式匹配
感觉这个就不用讲了, 都知道
-
函数参数解构的语义
Rust 的函数参数支持解构, 但不算在函数签名里, 就导致了和其他解构行为不一样
fn foo(ref bar: String) {} let x = String::new(); foo(x); // x moved here
-
闭包捕获环境的行为依赖
Copy
trait因为太长, 拆到这篇独立文章里
-
RawFd
缺少限制在许多库中, 接受任意 file descriptor 的 API 是这样写的:
pub fn do_some_io<FD: AsRawFd>(input: &FD) -> io::Result<()> { some_syscall(input.as_raw_fd()) }
但由于
RawFd
只是i32
的别名, 用户可以凭空造一个RawFd
传进去:do_some_io(&7i32)
很明显这是不应该的, 至少在 safe 的 Rust 中不应该允许, 是标准库设计上的安全漏洞
而要修复的话因为 Rust 不允许对 std 做破坏性变更, 没法直接修改
RawFd
, 所以 RFC#3128 提出的方案是加几个新的 fd 类型和 trait, 像NonZero
那样在构造时进行保证, 然后逐步让所有库替换掉旧的AsRawFd
实现. 可以在 #87074 中追踪进度