结构体自引用
rust
struct SelfRef<'a> {
value: String,
// 该引用指向上面的value
pointer_to_value: &'a str,
}
以上就是一个很简单的自引用结构体,看上去好像没什么,那来试着运行下:
rust
fn main(){
let s = "aaa".to_string();
let v = SelfRef {
value: s,
pointer_to_value: &s
};
}
运行后报错:
text
let v = SelfRef {
12 | value: s,
| - value moved here
13 | pointer_to_value: &s
| ^^ value borrowed here after move
因为我们试图同时使用值和值的引用,最终所有权转移和借用一起发生了。所以,这个问题貌似并没有那么好解决,不信你可以回想下自己具有的知识,是否可以解决?
unsafe
实现
rust
#[derive(Debug)]
struct SelfRef {
value: String,
ptr_to_value: *const String,
}
impl SelfRef {
fn new(txt: &str) -> Self {
SelfRef {
value: String::from(txt),
ptr_to_value: std::ptr::null(), // 裸指针
}
}
fn init(&mut self) {
let self_ref: *const String = &self.value;
self.ptr_to_value = self_ref;
}
fn value(&self) -> &str {
&self.value
}
fn ptr_to_value(&self) -> &String {
assert!(!self.ptr_to_value.is_null(),
"Test::b called without Test::init being called first");
unsafe { &*(self.ptr_to_value) }
}
}
fn main() {
let mut t = SelfRef::new("hello");
t.init();
// 打印值和指针地址
println!("{}, {:p}", t.value(), t.ptr_to_value());
}
在这里,我们在 ptr_to_value
中直接存储裸指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 unsafe
代码。
上面的代码你还能通过裸指针来修改 String
,但是需要将 *const
修改为 *mut
:
rust
#[derive(Debug)]
struct SelfRef {
value: String,
pointer_to_value: *mut String,
}
impl SelfRef {
fn new(txt: &str) -> Self {
SelfRef {
value: String::from(txt),
pointer_to_value: std::ptr::null_mut(),
}
}
fn init(&mut self) {
let self_ref: *mut String = &mut self.value;
self.pointer_to_value = self_ref;
}
fn value(&self) -> &str {
&self.value
}
fn pointer_to_value(&self) -> &String {
assert!(!self.pointer_to_value.is_null(), "Test::b called without Test::init being called first");
unsafe { &*(self.pointer_to_value) }
}
}
fn main() {
let mut t = SelfRef::new("hello");
t.init();
println!("{}, {:p}", t.value(), t.pointer_to_value()); // hello, 0x16f3aec70
t.value.push_str(", world");
unsafe {
(&mut *t.pointer_to_value).push_str("!");
}
println!("{}, {:p}", t.value(), t.pointer_to_value()); // hello, world!, 0x16f3aec70
}
除了使用unsafe
,还可以使用Pin
。
无法被移动的Pin
Pin
的使用可以固定住一个值,防止该值在内存中被移动。
通过开头我们知道,自引用最麻烦的就是创建引用的同时,值的所有权会被转移,而通过 Pin
就可以很好的防止这一点: