提问人:FreD 提问时间:4/5/2023 最后编辑:FreD 更新时间:4/6/2023 访问量:120
在 Rust 中设计一个具有可变 getter 和不可变 getter 的特征,并默认实现不可变的 getter
Design a trait in Rust with a mutable getter and an immutable getter and that implements the immutable getter by default
问:
我想设计一个具有可变和不可变吸气剂的特征,该特征适用于某个领域。
但是,我希望该特性的实现者只需要实现一种方法,通常是可变的 getter。self
我找到了这个解决方案(操场):
pub trait MyTrait<T> {
fn inside_mut(&mut self) -> &mut T;
fn inside_ref(&self) -> &T {
let ptr = self as *const Self as *mut Self;
&* unsafe { &mut *ptr }.inside_mut()
}
}
pub struct Data(String);
impl MyTrait<String> for Data {
fn inside_mut(&mut self) -> &mut String { &mut self.0 }
}
fn main() {
let mut data = Data(format!("Foo"));
let x: &String = data.inside_ref();
let y: &String = data.inside_ref();
println!("x -> {x}");
println!("y -> {y}");
*data.inside_mut() = format!("Bar");
println!("data.0 -> {}", data.0);
}
结果:
Standard Error
Compiling playground v0.0.1 (/playground)
Finished release [optimized] target(s) in 0.93s
Running `target/release/playground`
Standard Output
x -> Foo
y -> Foo
data.0 -> Bar
我的问题是:这种方法合理吗?
答:
2赞
Finomnis
4/5/2023
#1
不,这不合理。
用户可以在其实现中修改 ,这也将修改 中的结构。self
inside_mut
inside_ref
喜欢这个:
pub trait MyTrait<T> {
fn inside_mut(&mut self) -> &mut T;
fn inside_ref(&self) -> &T {
let ptr = self as *const Self as *mut Self;
&*unsafe { (&mut *ptr).inside_mut() }
}
}
#[derive(Debug)]
struct MyStruct {
value: i32,
}
impl MyTrait<i32> for MyStruct {
fn inside_mut(&mut self) -> &mut i32 {
self.value += 1;
&mut self.value
}
}
fn main() {
// Important: this is not `mut`!!!
let x = MyStruct { value: 42 };
println!("{:?}", x);
x.inside_ref();
println!("{:?}", x);
}
MyStruct { value: 42 }
MyStruct { value: 43 }
这意味着这个实现是不合理的:
被视为未定义的行为
- 更改不可变数据。项目内的所有数据都是不可变的。此外,通过共享引用或不可变绑定拥有的数据访问的所有数据都是不可变的,除非该数据包含在 .
const
UnsafeCell<U>
评论
0赞
FreD
4/5/2023
哦,是的!我没有看到这种微妙之处!
2赞
Chayim Friedman
4/5/2023
即使用户不这样做,它也是即时 UB,因为将共享引用转换为可变引用也是即时 UB,即使它没有突变。
0赞
FreD
4/5/2023
#2
在看了Finomnis的回答之后,我来到了这个解决方案(playground),miri不会产生UB警报:
use core::cell::UnsafeCell;
pub trait MyTrait<T> {
fn inside_mut(&mut self) -> &mut T {
let ptr = self.implement_ref().get();
unsafe { &mut *ptr }
}
fn inside_ref(&self) -> &T {
let ptr = self.implement_ref().get();
unsafe { & *ptr }
}
fn implement_ref(&self) -> &UnsafeCell<T>;
}
pub struct Data(UnsafeCell<String>);
impl Data {
pub fn new(s:String) -> Self {
Data(UnsafeCell::new(s))
}
}
impl MyTrait<String> for Data {
fn implement_ref(&self) -> &UnsafeCell<String> { &self.0 }
}
fn main() {
let mut data = Data::new(format!("Foo"));
let x: &String = data.inside_ref();
let y: &String = data.inside_ref();
println!("x -> {x}");
println!("y -> {y}");
*data.inside_mut() = format!("Bar");
println!("data.0 -> {}", data.inside_ref());
}
但是,我仍然有一个问题。miri 为以下代码 (playground) 生成 UB 警报:
pub trait MyTrait<T> {
fn inside_ref(&self) -> &T;
fn inside_mut(&mut self) -> &mut T {
let ptr = self.inside_ref() as *const T as *mut T;
unsafe { &mut *ptr }
}
}
pub struct Data(String);
impl MyTrait<String> for Data {
fn inside_ref(&self) -> &String { &self.0 }
}
fn main() {
let mut data = Data(format!("Foo"));
let x: &String = data.inside_ref();
let y: &String = data.inside_ref();
println!("x -> {x}");
println!("y -> {y}");
*data.inside_mut() = format!("Bar");
println!("data.0 -> {}", data.0);
}
但是 Finomnis 提供的很好的反例不适用于这种情况。有人能举例说明后一种情况的不合理性吗?
编辑:灵感来自cafce25(playground)的反例:
static IMMUTABLE_STATIC_STR: &'static str = "IMMUTABLE FOO";
pub trait MyTrait<T> {
fn inside_ref(&self) -> &T;
fn inside_mut(&mut self) -> &mut T {
let ptr = self.inside_ref() as *const T as *mut T;
unsafe { &mut *ptr }
}
}
pub struct Data;
impl MyTrait<&'static str> for Data {
fn inside_ref(&self) -> &&'static str { &IMMUTABLE_STATIC_STR }
}
fn main() {
let mut data = Data;
let x: &&'static str = data.inside_ref();
let y: &&'static str = data.inside_ref();
println!("x -> {x}");
println!("y -> {y}");
*data.inside_mut() = "Bar";
println!("data.inside_ref() -> {}", data.inside_ref());
}
结果:
Standard Error
Compiling playground v0.0.1 (/playground)
Finished release [optimized] target(s) in 1.61s
Running `target/release/playground`
Standard Output
x -> IMMUTABLE FOO
y -> IMMUTABLE FOO
data.inside_ref() -> IMMUTABLE FOO
评论
0赞
Chayim Friedman
4/5/2023
首先,对于新问题,请提出一个新的 SO 问题。其次,有效,但这是一个坏主意。最好只实现两种方法。UnsafeCell
0赞
Chayim Friedman
4/5/2023
美里不(也不能)检查健全性。它只检查程序的一次执行是否包含 UB。
0赞
FreD
4/5/2023
好吧!我不认为一个新问题是值得的。谢谢你的建议!
0赞
FreD
4/5/2023
我同意你的看法,应该避免不安全的方法。但是,在某些情况下,特征设计者希望确保两种方法都引用相同的数据。也就是说,无论如何,通过仅实现可变吸气器并处理诱导约束,都可以避免该问题。
1赞
cafce25
4/5/2023
fn inside_ref(&self) -> &T { &IMMUTABLE_STATIC_T }
呃哦。
评论