英文:
How to cast Vec<Box<dyn SomeTrait + Send + Sync>> to Vec<Box<dyn SomeTrait + Sync>> in rust?
问题
I'm failing to cast dyn SomeTrait + Send + Sync
to dyn SomeTrait + Sync
. More specifically, I am trying to cast &Vec<Box<dyn SomeTrait + Send + Sync>>
to &Vec<Box<dyn SomeTrait + Sync>>
.
我无法将 dyn SomeTrait + Send + Sync
强制转换为 dyn SomeTrait + Sync
。更具体地说,我试图将 &Vec<Box<dyn SomeTrait + Send + Sync>>
转换为 &Vec<Box<dyn SomeTrait + Sync>>
。
I tried simply reassining as suggested here as well as using as
but neither approach worked.
我尝试了简单地重新分配 如此处建议的,以及使用 as
,但都没有成功。
How can I accomplish what I want?
我该如何实现我想要的效果?
The error message is
错误消息是
expected trait ToSql + Sync
, found trait ToSql + Send + Sync
预期的特质是 ToSql + Sync
,但找到的特质是 ToSql + Send + Sync
Edit: Also, it would be nice to know, why this doesn't just work, since the conversion should be possible because we know that each dyn SomeTrait + Send + Sync>
satisfies SomeTrait + Sync
编辑:另外,了解为什么这不起作用会很好,因为转换应该是可能的,因为我们知道每个 dyn SomeTrait + Send + Sync>
都满足 SomeTrait + Sync
。
英文:
I'm failing to cast dyn SomeTrait + Send + Sync
to dyn SomeTrait + Sync
.
More specifically, I am trying to cast &Vec<Box<dyn SomeTrait + Send + Sync>>
to &Vec<Box<dyn SomeTrait + Sync>>
.
I tried simply reassining as suggested here as well as using as
but neither approach worked.
How can I accomplish what I want?
The error message is
expected trait `ToSql + Sync`, found trait `ToSql + Send + Sync`
Edit: Also, it would be nice to know, why this doesn't just work, since the conversion should be possible because we know that each dyn SomeTrait + Send + Sync>
satisfies SomeTrait + Sync
答案1
得分: 2
以下是翻译好的部分:
让我们从困难的部分开始。如果你想要一个简单的解决方案,请跳到答案的最后一部分。
所以,事实是,它确实可以工作。为什么不行呢?嗯,显然有一些问题,但主要问题可能是没有人去解决它。在 internals.rust-lang.org 上有一个讨论。
首先要做的事情是,我们需要理解“强制转换”和“子类型化”的区别。
将类型 T
强制转换为 U
意味着当你有一个类型为 T
的东西,并且你想将它转换为类型 U
时,编译器可以自动为你完成。你只需要声明你想要类型 U
,编译器就会进行必要的处理并为你完成转换。例如,数组的引用(&[T; N]
)可以强制转换为切片的引用(&[T]
)。当你有一个数组的引用并将其传递给某个函数(例如,一个函数)需要切片的引用时,编译器会自动插入代码以将数组的指针(在汇编代码中是数组的引用)转换为指针 + 大小(在汇编代码中是切片的引用)。通常来说,每个转换都可以是一种强制转换。它只需要内置到语言中,并且编译器必须知道如何插入代码来执行它。然而,Rust 的原则是“昂贵的事情应该是显式的”,因此,强制转换(它们是隐式的)必须是廉价的。
对我们理解强制转换的重要事情是它们“不是可传递的”。也就是说,如果类型 T
可以被强制转换为 U
,这并不意味着 G<T>
可以被强制转换为 G<U>
。或者更简单地说,&[T; N]
可以强制转换为 &[T]
并不意味着我们也可以将 Vec<&[T; N]>
强制转换为 Vec<&[T]>
。原因很简单:因为强制转换需要实际插入代码来执行转换,我们不知道如何为包装类型生成代码。我们知道如何将 &[T; N]
转换为 &[T]
(只需附加大小),并不意味着我们知道如何将 &[T; N]
的向量转换为 &[T]
的向量,这需要分配和复制(因为 &[T]
的大小是 &[T; N]
的大小的两倍)。
“子类型关系”是完全不同的东西。如果类型 T
是类型 U
的子类型,这意味着“任何 T
也是 U
”。经典示例是面向对象编程语言中的基类。Derived
的实例也是 Base
的实例。这确实意味着,与强制转换一样,Derived
的指针可以转换为 Base
的指针,但与强制转换不同,这不需要插入任何转换代码。
截至撰写本文时,Rust 中唯一的子类型关系是生命周期关系。例如,'static
是任何生命周期的子类型,这意味着 &'static T
是任何 &'a T
的子类型。这就是为什么可以在需要 &'a T
的地方提供 &'static T
的原因。
由于不需要代码来进行转换,子类型关系是可传递的。你可以将 Vec<&'static T>
转换为 Vec<&'a T>
。唯一的例外是协变性,我现在不会谈论它。
现在来谈谈重要的问题:从 dyn Trait + Send + Sync
到 dyn Trait + Sync
的转换(换句话说,从 dyn Trait
中去除额外的自动特性约束)是一种强制转换而不是子类型化。这就是为什么你不能将 &Vec<Box<dyn Trait + Send + Sync>>
转换为 &Vec<Box<dyn Trait + Sync>>
的原因 - 这种转换不可传递。
没有理由它不能是子类型化 - 除了已经说过的,除了需要代码外,不需要任何其他东西。
但我们还能做吗?
使用不安全代码,是的;但也不是的。
从 &Vec<Box<dyn Trait + Send + Sync>>
到 &Vec<Box<dyn Trait + Sync>>
的转换是不允许的,没有任何条件。即使假设 Box<dyn Trait + Send + Sync>
和 Box<dyn Trait + Sync>
具有相同的布局,包含它们的两个 Vec
也不能保证具有相同的布局。所以你要求的东西无法实现。
但是有类似的东西可以实现吗:从 Vec<Box<dyn Trait + Send + Sync>>
到 Vec<Box<dyn Trait + Sync>>
(没有引用)。或者类似地
英文:
Let's start with the hard things. If you want an easy solution, jump to the end of the answer (the last section).
So: the fact is, it indeed could work. Why doesn't it? Well, apparently there are some problems, but the main problem is probably that nobody worked on that. There is a thread in internals.rust-lang.org discussing that.
First thing to do, we need to understand the difference between coercion and subtyping.
Coercing type T
to U
means that when you have something of type T
and you want to convert it to type U
the compiler can do that for you automagically. You just need to declare that you want type U
and the compiler will do its magic and give it for you. For example, a reference to an array (&[T; N]
) it can be coerced to a reference to slice (&[T]
). When you have a reference to an array and you pass it to something (e.g. a function) that expects a reference to a slice, the compiler automatically inserts code to convert the pointer to the array (what a reference to array is in assembly code) to a pointer + size (what a reference to slice is in assembly code).
Generally, every conversion can be a coercion. It just needs to be built into the language, and the compiler has to know how to insert code to do it. However, Rust has the principle that expensive things should be explicit, and so, coercions (that are implicit) need to be cheap.
The important thing for us to understand about coercions is that they're not transitive. That is, if type T
can be coerced to U
, that doesn't mean that G<T>
can be coerced to G<U>
. Or, in simpler words, the fact that &[T; N]
can be coerced to &[T]
doesn't mean that we can also coerce Vec<&[T; N]>
to Vec<&[T]>
. The reason is simple: since coercion needs to actually insert code to perform the conversion, we don't know how to generate the code for the wrapper type. The fact that we know to convert &[T; N]
to &[T]
(by simply attaching the size) doesn't mean we know to convert a vector of &[T; N]
s to a vector of &[T]
s, a thing that requires allocating and copying (since the size of &[T]
is double the size of &[T; N]
).
A subtyping relationship is a completely different thing. If a type T
is a subtype of U
, that means that any T
is also U
. The classic example is base classes in OOP languages. A instance of Derived
is also an instance of Base
. This does mean that, like with coercions, a pointer to Derived
can be converted to a pointer to Base
, but unlike with coercions, this doesn't require inserting any conversion code.
The only subtypings in Rust as of time of writing are lifetime relationships. For example, 'static
is a subtype of any lifetime, and that means that &'static T
is a subtype of any &'a T
. This is why you can give &'static T
where &'a T
is expected.
Because no code to convert is required, subtyping is transitive. You can convert Vec<&'static T>
to Vec<&'a T>
. The exception to that is variance, which I won't talk about now.
Now to the important point: the conversion from dyn Trait + Send + Sync
to dyn Trait + Sync
(in other words, the removal of additional auto-trait bounds from dyn Trait
) is a coercion and not subtyping. This is why you cannot convert &Vec<Box<dyn Trait + Send + Sync>>
to &Vec<Box<dyn Trait + Sync>>
- the conversion is not transitive.
There is no reason why it couldn't be subtyping - no code is required for the conversion, other than that, as I already said, nobody worked on that.
But can we still do that?
Using unsafe code, yes; but also no.
The conversion from &Vec<Box<dyn Trait + Send + Sync>>
to &Vec<Box<dyn Trait + Sync>>
is not allowed, period. Even assuming Box<dyn Trait + Send + Sync>
and Box<dyn Trait + Sync>
have the same layout, two Vec
s containing them is not guaranteed to have the same layout too. So what you asked for cannot be done.
But how about something similar: converting from Vec<Box<dyn Trait + Send + Sync>>
to Vec<Box<dyn Trait + Sync>>
(no reference). Or similarly, &[Box<dyn Trait + Send + Sync>]
to &[Box<dyn Trait + Sync>]
. Or even &Vec<Box<dyn Trait + Send + Sync>>
to &[&(dyn Trait + Send + Sync)]
?
We can use Vec::from_raw_parts()
(or std::slice::from_raw_parts()
) to build the Vec
(or the slice) without relying on its layout specifics. But we still need that a pointer to dyn Trait + AutoTrait
will have the same layout as a pointer to dyn Trait
. Does this hold?
Well, currently, yes; and I believe it will also hold in the future. But it is not documented anywhere, so I would not rely on that.
However, from your comments, it looks like all you want is to get a slice &[&(dyn SomeTrait + Sync)]
and you do not even care if you allocate or not. Well, this can be done very easily:
let v2: Vec<&(dyn SomeTrait + Sync)> = v.iter().map(|item| &**item as _).collect();
let v2: &[&(dyn SomeTrait + Sync)] = v2.as_slice();
答案2
得分: 0
你可以使用以下代码实现:
let my_new_vec: Vec<_> = my_old_vec
.into_iter()
.map(|x| Box<dyn SomeTrait + Sync>{x})
.collect();
编译器有可能能够优化掉循环。
英文:
You can do it using this:
let my_new_vec: Vec<_> = my_old_vec
.into_iter()
.map(|x|->Box<dyn SomeTrait + Sync>{x})
.collect();
There is a chance that compiler would be able to optimize loop away.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论