std::marker::Sized 的虚假要求
Spurious requirement for std::marker::Sized
更新:这个更简单的代码 (play) 表现出相同的症状:
use std::fmt::Display;
pub fn arg(a: &str, b: &Display) {
}
fn main() {
arg("foo", "bar");
}
给出错误:
<anon>:7:16: 7:21 error: the trait `core::marker::Sized` is not implemented for the type `str` [E0277]
<anon>:7 arg("foo", "bar");
^~~~~
<anon>:7:16: 7:21 note: `str` does not have a constant size known at compile-time
<anon>:7 arg("foo", "bar");
^~~~~
但我看不出有任何理由在这里要求尺寸。 a
和b
是一样对待的,是同一个类型,a
是没有问题的。所以
- 为什么
b
和 有问题
- 如何告诉它 Trait 的大小不应已知。
事实上,它是一种特征类型,无论底层类型是什么,它的类型都是未知的。那为什么要大小呢?
哦,我不应该忘记:在实际用例中我需要动态多态性。函数(方法)将与不同实际类型的参数和存储的引用链接(参见下面的原始展示),所以我不能将它转换为T: Trait + ?Sized
。
原始展品:
我有一些代码如下 (see also on play.rust-lang.org):
pub trait Trait { /* some methods */ }
impl Trait for str { /* some implementation */ }
pub struct Struct<'a> {
args: HashMap<&'a str, &'a Trait>,
}
impl<'a> Struct<'a> {
pub fn new() -> Self {
Struct { args: HashMap::new() }
}
pub fn arg(mut self, key: &'a str, value: &'a Trait) -> Struct<'a> {
self.args.insert(key, value);
return self;
}
// of course there is something to process the collected arguments too
}
fn main() {
Struct::new().arg("foo", "bar");
}
这给了我错误:
test.rs:32:30: 32:35 error: the trait `core::marker::Sized` is not implemented for the type `str` [E0277]
test.rs:32 Struct::new().arg("foo", "bar");
^~~~~
test.rs:32:30: 32:35 note: `str` does not have a constant size known at compile-time
test.rs:32 Struct::new().arg("foo", "bar");
错误并非虚假。
特征对象的原始表示是这样的 (std::raw::TraitObject
):
#[repr(C)]
pub struct TraitObject {
pub data: *mut (),
pub vtable: *mut (),
}
对象的实际数据在单个指针后面。
但是动态大小的类型呢?以切片(&[T]
)为例,它们是这样的形式:
#[repr(C)]
pub struct Slice<T> {
pub data: *const T,
pub len: usize,
}
这个引用是两个字:一个指向切片开始的指针和切片中元素的数量。
因此 &T
的大小 实际上不是常量 。如果T
是Sized
,&T
和Box<T>
将是一个词,但如果T
不是Sized
,&T
和Box<T>
将是 两个 个单词。
let word = std::mem::size_of::<usize>();
// References to sized types: one word.
assert_eq!(std::mem::size_of::<&()>(), 1 * word);
assert_eq!(std::mem::size_of::<&u8>(), 1 * word);
assert_eq!(std::mem::size_of::<&String>(), 1 * word);
// References to unsized types: two words.
assert_eq!(std::mem::size_of::<&[u8]>(), 2 * word);
assert_eq!(std::mem::size_of::<&str>(), 2 * word);
assert_eq!(std::mem::size_of::<&std::path::Path>(), 2 * word);
这有什么影响?嗯,正如前面所说,特征对象的定义要求数据指针只有一个字长。要存储一个动态大小的类型需要两个个词;我没有太多考虑是否 可能 将所有特征对象膨胀为具有两个数据词的实用性(其中一个在大小对象的情况下是多余的) ,所以它可能会也可能不会,但是语言已经决定不支持动态大小类型的特征对象。
因此:如果你想创建一个特征对象,你需要使用一个大小的类型,比如 &str
,而不是一个动态大小的类型,比如 str
。这意味着 (x: &&str) as &std::fmt::Display;
:
arg("foo", &"bar");
更新:这个更简单的代码 (play) 表现出相同的症状:
use std::fmt::Display;
pub fn arg(a: &str, b: &Display) {
}
fn main() {
arg("foo", "bar");
}
给出错误:
<anon>:7:16: 7:21 error: the trait `core::marker::Sized` is not implemented for the type `str` [E0277]
<anon>:7 arg("foo", "bar");
^~~~~
<anon>:7:16: 7:21 note: `str` does not have a constant size known at compile-time
<anon>:7 arg("foo", "bar");
^~~~~
但我看不出有任何理由在这里要求尺寸。 a
和b
是一样对待的,是同一个类型,a
是没有问题的。所以
- 为什么
b
和 有问题
- 如何告诉它 Trait 的大小不应已知。
事实上,它是一种特征类型,无论底层类型是什么,它的类型都是未知的。那为什么要大小呢?
哦,我不应该忘记:在实际用例中我需要动态多态性。函数(方法)将与不同实际类型的参数和存储的引用链接(参见下面的原始展示),所以我不能将它转换为T: Trait + ?Sized
。
原始展品:
我有一些代码如下 (see also on play.rust-lang.org):
pub trait Trait { /* some methods */ }
impl Trait for str { /* some implementation */ }
pub struct Struct<'a> {
args: HashMap<&'a str, &'a Trait>,
}
impl<'a> Struct<'a> {
pub fn new() -> Self {
Struct { args: HashMap::new() }
}
pub fn arg(mut self, key: &'a str, value: &'a Trait) -> Struct<'a> {
self.args.insert(key, value);
return self;
}
// of course there is something to process the collected arguments too
}
fn main() {
Struct::new().arg("foo", "bar");
}
这给了我错误:
test.rs:32:30: 32:35 error: the trait `core::marker::Sized` is not implemented for the type `str` [E0277]
test.rs:32 Struct::new().arg("foo", "bar");
^~~~~
test.rs:32:30: 32:35 note: `str` does not have a constant size known at compile-time
test.rs:32 Struct::new().arg("foo", "bar");
错误并非虚假。
特征对象的原始表示是这样的 (std::raw::TraitObject
):
#[repr(C)]
pub struct TraitObject {
pub data: *mut (),
pub vtable: *mut (),
}
对象的实际数据在单个指针后面。
但是动态大小的类型呢?以切片(&[T]
)为例,它们是这样的形式:
#[repr(C)]
pub struct Slice<T> {
pub data: *const T,
pub len: usize,
}
这个引用是两个字:一个指向切片开始的指针和切片中元素的数量。
因此 &T
的大小 实际上不是常量 。如果T
是Sized
,&T
和Box<T>
将是一个词,但如果T
不是Sized
,&T
和Box<T>
将是 两个 个单词。
let word = std::mem::size_of::<usize>();
// References to sized types: one word.
assert_eq!(std::mem::size_of::<&()>(), 1 * word);
assert_eq!(std::mem::size_of::<&u8>(), 1 * word);
assert_eq!(std::mem::size_of::<&String>(), 1 * word);
// References to unsized types: two words.
assert_eq!(std::mem::size_of::<&[u8]>(), 2 * word);
assert_eq!(std::mem::size_of::<&str>(), 2 * word);
assert_eq!(std::mem::size_of::<&std::path::Path>(), 2 * word);
这有什么影响?嗯,正如前面所说,特征对象的定义要求数据指针只有一个字长。要存储一个动态大小的类型需要两个个词;我没有太多考虑是否 可能 将所有特征对象膨胀为具有两个数据词的实用性(其中一个在大小对象的情况下是多余的) ,所以它可能会也可能不会,但是语言已经决定不支持动态大小类型的特征对象。
因此:如果你想创建一个特征对象,你需要使用一个大小的类型,比如 &str
,而不是一个动态大小的类型,比如 str
。这意味着 (x: &&str) as &std::fmt::Display;
:
arg("foo", &"bar");