关于Rust Refs与Derefs的理解正确性确认请求
案例1
fn main() { let x = 5; let y = x; assert_eq!(5, y); println!("y = {}", y); }
Q1:若使用assert_eq!(5, &y)会报错“no implementation for {integer} == &{integer}”,是否因为y作为原始类型在栈上克隆了x的值5?
A1:完全正确。整数这类原始类型实现了Copy trait,执行let y = x时会直接在栈上复制x的值,y是一个独立的i32类型变量。当你写&y时,得到的是&i32类型的引用,而assert_eq!要求左右两边类型必须能匹配(或实现对应的PartialEq),Rust并没有为{integer}和&{integer}实现直接比较的逻辑,所以会报错。
案例2
fn main() { let x = 5; let y = &x; assert_eq!(5, *y); println!("y = {}", y); }
Q2:这是否是原始类型场景下强制创建引用而非克隆的方式?此时y是否存储了x的栈地址?
A2:是的。let y = &x明确创建了x的不可变引用,y的类型是&i32,它存储的就是x在栈上的内存地址。通过*y解引用这个地址,就能获取到x的实际值5,所以assert_eq!(5, *y)可以正常执行。
案例3
fn main() { let x = String::from("Five"); let y = &x; assert_eq!("Five", y); println!("y = {}", y); }
Q3:若x是堆分配的String,使用assert_eq!("Five", y)即可生效(无需*y),是否因为编译器的隐式解引用?
A3:没错,这是**Deref强制转换(Deref coercion)**和标准库的PartialEq实现共同作用的结果。String类型实现了Deref<Target = str>,所以&String会被编译器自动隐式转换为&str;而字符串字面量"Five"的类型本身就是&str,两者类型匹配,同时标准库为&str和&String(或者说转换后的&str)实现了PartialEq,因此可以直接用assert_eq!比较。
案例4
fn main() { let x = 5; let &y = &x; let &z = &&x; assert_eq!(5, *z); println!("y = {}", z); }
Q4:该代码中assert_eq!(5, *z)可行,但assert_eq!(5, z)不行,这是否不属于链式引用的自动解析场景?
A4:这和链式引用自动解析无关,本质是模式匹配的结果:
let &y = &x:&x是&i32类型,模式&y会匹配这个外层引用,把引用指向的x的值(5)绑定给y,所以y的类型是i32。let &z = &&x:&&x是&&i32类型,模式&z匹配的是最外层的那个引用,把内层的&i32绑定给z,所以z的类型是&i32,而非i32。
因此assert_eq!(5, z)会因为类型不匹配(i32 vs &i32)报错,而*z解引用后得到i32类型的值,就能和5正常比较。
内容的提问来源于stack exchange,提问作者bonchef

