移动盒子特征



我从Rust开始——编码的第三天(来自C++、Java等(

我希望使用以下内容来构建库存系统:

struct MeleeWeapon {...}
struct RangedWeapon {...}
struct Food {...}
struct Armour {...} // etc.
trait Item {...}
trait Weapon {...} // etc.
// these traits are implemented as necessary for the above structs, noting that all of them implement trait Item.

现在我已经列出了项目,我希望创建一个库存并允许将项目添加到其中。我知道Boxes类似于C++智能指针,并希望将项目从其原始所有者移动到项目ArrayVec。

struct Inventory {
items: ArrayVec<Box<dyn Item>, 128>
}
impl Inventory {
fn add<'a>(&self, item:  Box< &'a dyn Item>) -> bool {
if self.items.is_full() {
return false;
}
self.items.push(&item); // error
return true;
}
}

我得到的错误如下:

mismatched types
expected struct `Box<(dyn Item + 'static)>`
found reference `&Box<&'a (dyn Item + 'a)>`

唉,我觉得我错过了一些关于Rust如何管理指针的基本知识,但我对Rust的了解还不够,无法找到正确的解决方案。如果有人已经回答了这个问题——我确信他们可能已经回答了——我就不知道用谷歌搜索这个词了…

我假设您的最小可复制示例如下:

use arrayvec::ArrayVec;
trait Item {}
struct Inventory {
items: ArrayVec<Box<dyn Item>, 128>,
}
impl Inventory {
fn add<'a>(&self, item: Box<&'a dyn Item>) -> bool {
if self.items.is_full() {
return false;
}
self.items.push(&item); // error
return true;
}
}
error[E0308]: mismatched types
--> src/lib.rs:14:25
|
14  |         self.items.push(&item); // error
|                    ---- ^^^^^ expected struct `Box`, found reference
|                    |
|                    arguments to this function are incorrect
|
= note: expected struct `Box<(dyn Item + 'static)>`
found reference `&Box<&'a (dyn Item + 'a)>`
note: associated function defined here
--> /home/martin/.cargo/registry/src/github.com-1ecc6299db9ec823/arrayvec-0.7.2/src/arrayvec.rs:175:12
|
175 |     pub fn push(&mut self, element: T) {
|            ^^^^

您的代码中有三个独立的问题。

首先,将项目推入向量需要将所有权移交给向量。您可以通过在中传递引用来防止这种情况发生。请执行self.items.push(item)而不是self.items.push(&item)

第二个问题是您接受Box<&'a dyn Item>作为参数。包含&引用的Box几乎没有意义,而且与存储在items向量中的类型不匹配。请改用Box<dyn Item>作为参数类型。

第三个问题是,实际上不能修改items变量,因为函数采用&self,这意味着self是不可变的。请改用&mut self

这是更正后的代码:

use arrayvec::ArrayVec;
trait Item {}
struct Inventory {
items: ArrayVec<Box<dyn Item>, 128>,
}
impl Inventory {
fn add(&mut self, item: Box<dyn Item>) -> bool {
if self.items.is_full() {
return false;
}
self.items.push(item);
return true;
}
}

进一步说明,尽管可以说是个人偏好:

ArrayVec提供了一个try_push方法,它已经完成了您试图实现的任务。

因此,您可以将代码缩减为:

use arrayvec::ArrayVec;
trait Item {}
struct Inventory {
items: ArrayVec<Box<dyn Item>, 128>,
}
impl Inventory {
fn add(&mut self, item: Box<dyn Item>) -> bool {
self.items.try_push(item).is_ok()
}
}

另请注意:将Box传递到ArrayVec会将其所有权转移到ArrayVec。如果try_push失败,则该对象将被销毁。这就是为什么try_push在失败时返回一个CapacityError,将您推送的对象交给您进行进一步处理。


还有一句话:我怀疑你使用Box<&dyn Item>是因为你想把原始对象放在其他地方。

Box无法实现这种情况;您将不得不使用像RcArc这样的参考计数器(取决于您是否需要线程安全性(。但是,请注意,这将使项不可变,并且您必须阅读内部可变性的概念。

长话短说,如果你想保留原始对象,根据线程安全要求,你可能想使用Rc<RefCell<dyn Item>>Arc<Mutex<dyn Item>>作为ArrayVec的项目类型。

push需要一个owned类型,但您提供了一个引用。

这就是错误所在;ExpecedBox发现参考CCD_;正在谈论。

但是,我不建议为类似库存的结构存储Boxed Trait对象。因为您无法从特征对象中取回具体对象。Plus Box是堆分配的,需要在运行时进行动态调度,因此性能相对较差。

请改用enum,它们适用于这些用例。

struct MeleeWeapon1 {...}
struct MeleeWeapon2 {...}
struct RangedWeapon1 {...}
struct RangedWeapon2 {...}
struct Helmet1 {...}
struct Food1 {...}
trait RangedWeaponTrait {...}
trait MeleeWeaponTrait {...}
trait ArmorTrait {...}
trait FoodTrait {...}
enum MeleeWeapon {
MeleeWeapon1(MeleeWeapon1),
MeleeWeapon2(MeleeWeapon2),
}
enum RangedWeapon {
RangedWeapon1(RangedWeapon1),
RangedWeapon2(RangedWeapon2),
}
enum Weapon {
Melee(MeleeWeapon),
Ranged(RangedWeapon),
}
enum Helmet {
Helmet1(Helmet1),
}
/* Other armor types */
enum Armor {
Helmet(Helmet),
/* Other armor types */
}
enum Food {
Food1(Food1),
}
enum Item {
Weapon(Weapon),
Armor(Armor),
Food(Food),
}
struct Inventory {
items: ArrayVec<Item, 128>,
}
impl Inventory {
pub fn add(&mut self, item: Item) -> Result<(), Item> {
if self.items.is_full() {
Err(item)
} else {
self.items.push(item);
Ok(())
}
}
}

最新更新