-
Notifications
You must be signed in to change notification settings - Fork 173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[第三章] 关于trait的Self类型参数不能被限定为Sized #137
Comments
@ChyuWei 感谢反馈。我在考虑修改描述,后续讨论。 |
看了RFC,和之前群里讨论理解的差不多 那么,假如Rust放宽限制,究竟Sized类型会不会导致安全问题呢? 下面我假设Rust不限制来看一下两种情况的区别:
可以看出如果允许Sized类型创建trait object唯一的区别就是dyn SomeTrait是否实现了SomeTrait,对于能创建trait object的类型约束没有任何变化,并不会导致安全问题的区别。 正如rfc0255中说的,加以限制,是为了更好的编译错误提示、减少软件迭代产生的困扰(可能原设计更容易导致breaking change?)、更好的设计,相应的缺点就是限制了灵活性。 |
相关 : #51 |
今天有时间好好翻了下相关RFC,我发现我一直掉在一个坑里:「安全」。我一直在寻找「对象安全」在安全性上的 意义,但我发现,「对象安全」和安全性并没有关系,它关乎的是vtable的方法调度问题。 所以,一个trait,只有当编译器可以自动为其实现自身的时候,才是对象安全的。方法通过存储在trait对象的vtable中,将每个方法实现为动态的函数调用。如果没有对象安全规则,虽然可以编写满足trait对象的类型签名的函数,但是其内部无法实际使用trait对象。 trait Foo {
fn method_a(&self) -> u8;
fn method_b(&self, x: f32) -> String;
}
// automatically inserted by the compiler
impl<'a> Foo for Foo+'a {
fn method_a(&self) -> u8 {
// dynamic dispatch to `method_a` of erased type
self.method_a()
}
fn method_b(&self, x: f32) -> String {
// as above
self.method_b(x)
}
} |
补充 我写书的时候,看见「安全」这两个字,就自动脑补了一个解释:「对象安全肯定和内存安全相关」。导致我想当然了。 trait对象,在运行时已经擦除了类型信息,要通过虚表调用相应的方法。不像静态分发那样,trait对象不是为每个类型都实现trait的方法,而是只实现一个副本(自动为其实现自身),结合虚函数去调用。 现在想一个问题: 假如那个类型没有实现这个方法怎么办?实际上,会有很多种情况下,会出现这个问题。运行时确定的类型和方法应该合法的,保证trait对象在运行时可以安全地调用相关的方法。 比如trait里有泛型函数。这就搞的很复杂了,可能运行时无法确定该调用哪个函数。反正是各种情况吧。所以,为了避免出现这种问题,官方引入了对象安全的概念。实际上就是引入了一系列的规则,也就是书里列出的那些。编译器根据这些规则,在编译期判断一个你写的trait对象,是不是合法的。 比如:trait对象其实在内部维护两个表:safe_vtable和nonself_vtable,标记有where Self: Sized 的会被归类到nonself_vtable,也就是说,不会被trait对象调用。 如果是合法的,则代表了,这个trait对象在运行时调用方法应该是没问题的。不会出现没有实现,或者不知道该调用哪个的情况。这就是对象安全的概念。 它和内存安全并无直接关系。 这里有个讨论值得查看,里面探讨了trait对象设计的历史相关内容: https://internals.rust-lang.org/t/trait-objects-blocking-entire-traits-vs-blocking-members/8796/7 |
合并: #213 |
代码清单3-40修正: // 对象不安全的trait
trait Foo {
fn bad<T>(&self, x: T);
fn new() -> Self;
}
// 将对象不安全的泛型方法拆分为独立的trait
trait Bar {
fn bad<T>(&self, x: T);
}
// Foo就成为了对象安全的trait
trait Foo: Bar {
fn new() -> Self;
}
// 对象安全的trait,使用where子句
trait Foo {
fn new() -> Self where Self: Sized;
} |
对象安全相关描述修改为:
|
@ZhangHanDong 关于代码清单3-40,修正后 // 将对象不安全的泛型方法拆分为独立的trait
trait Bar {
fn bad<T>(&self, x: T);
}
// Foo就成为了对象安全的trait
trait Foo: Bar {
fn new() -> Self;
} 有两点没看明白
|
同样的困惑😳 |
struct Name {
name: String,
}
trait Foo1 {
fn bad<T>(&self, x: T);
}
trait Foo {
fn new() -> Self where Self: Sized;
fn say(&self);
}
impl Foo for Name {
fn new() -> Self where Self: Sized {
Name { name: "rust".to_string() }
}
fn say(&self) {
println!("name: {:?}", self.name);
}
}
impl Foo1 for Name {
fn bad<T>(&self, x: T) {
x;
}
}
fn dyn_dispatch(d : &dyn Foo) {
d.say();
}
#[test]
fn test_trait_safety() {
let name: Name = Foo::new();
dyn_dispatch(&name);
} 1: |
dyn trait 既然是由数据指针和 vtable指针构成,为什么是unsized |
页码与行数
对于
文中试图解释为什么不能对trait添加Self:Sized限定:
对于trait Foo, 应该对所有实现Foo的类型都要求为Sized. 所以在后面应该不会有Unsize 的类型.
https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md
https://github.com/rust-lang/rfcs/blob/master/text/0546-Self-not-sized-by-default.md
根据这两个rfc, 貌似只是说为了保证
trait object必须实现当前trait
所以把Sized限定去掉了.所以书中那部分描述可能不太完善.
The text was updated successfully, but these errors were encountered: