2
struct MyCell<T> {
    value: T
}

impl<T> MyCell<T> {
    fn new(value: T) -> Self {
        MyCell { value }
    }
    
    fn get(&self) -> &T {
        &self.value
    }
    
    fn set(&self, new_value: T) {
        unsafe { 
            *(&self.value as *const T as *mut T) = new_value; 
        }
    }
}

fn set_to_local(cell: &MyCell<&i32>) {
    let local = 100;
    cell.set(&local);
}

fn main() {
    let cell = MyCell::new(&10);
    set_to_local(&cell);
}

调用时cell.set(&local),假设cellis'x'&localis 'y,我被告知协方差规则将改变cellfrom &MyCell<'x, &i32>to的类型&MyCell<'y, &i32>

unsafe 块内的赋值如何影响参数的生命周期推断set()?原始指针没有生命周期,那么编译器如何知道它应该使用协方差生成cellnew_value具有相同的生命周期?

4

1 回答 1

10

unsafe 块内的赋值如何影响参数的生命周期推断set()

它没有——这甚至不是特定于原始指针或unsafe. 函数体从不影响函数签名的任何方面(async fn这里不相关的 s 除外)。

原始指针没有生命周期,那么编译器如何知道它应该使用协方差生成cellnew_value具有相同的生命周期?

听起来你误读了一些建议。在您拥有的代码中,Cell<T>在 中是不变的T,但是要使内部可变类型可靠,它在类型参数中必须是不变的(而不是协变的)。在您拥有的代码中,编译器推断协方差,T因为MyCell包含一个简单的类型字段T。协方差是大多数泛型类型的“典型”情况。

因此,您拥有的代码是不健全的,因为MyCell它是协变的,T但必须是不变的T

您的代码也不健全,因为在 的实现中set(),您正在创建对 a 的不可变引用T&self.value然后写入其所指对象。不管你怎么做,这都是“未定义的行为” ,因为向编译器/优化器创建&self.value断言,指向的内存在引用被删除之前不会被修改。

如果您想重新实现标准库的Cell,您必须像标准库一样使用UnsafeCell原语:

pub struct Cell<T: ?Sized> {
    value: UnsafeCell<T>,
}

UnsafeCell是您选择退出&' 不变性保证的方式:创建 an&UnsafeCell<T> 并不会断言T不会发生突变。它在 中也是不变的T,这会自动使包含类型在 中保持不变T。这两个在这里都是必要的。

于 2022-02-25T05:06:03.937 回答