Using rust 1.2.0
使用铁锈1.2.0
Problem
问题
I'm still in the process of learning Rust (coming from a Javascript background) and am trying to figure out if it is possible for one struct StructB
to extend an existing struct StructA
such that StructB
has all the fields defined on StructA
.
我仍然处于学习Rust的过程中(来自于一个Java脚本背景),并且正在尝试弄清楚是否有可能让一个结构StructB扩展现有的结构StructA,从而使StructB具有在StructA上定义的所有字段。
In Javascript (ES6 syntax) I could essentially do something like this...
在Java脚本(ES6语法)中,我基本上可以做这样的事情...
class Person {
constructor (gender, age) {
this.gender = gender;
this.age = age;
}
}
class Child extends Person {
constructor (name, gender, age) {
super(gender, age);
this.name = name;
}
}
Constraints
制约因素
StructA
is from an external cargo
package that I have no control over.
Current Progress
目前的进展
I found this blog post on single-inheritance which sounds like exactly what I need.
我找到了这篇关于单一继承的博客文章,听起来正是我所需要的。
But trying to implement it resulted in this error message error: virtual structs have been removed from the language
. Some searching later and I found out that it had been implemented and then removed per RFC-341 rather quickly.
但尝试实现它会导致此错误消息错误:虚结构已从语言中删除。后来进行了一些搜索,我发现它已经实现,然后根据RFC-341相当快地删除了它。
Also found this thread about using traits, but since StructA
is from an external cargo package I don't think it is possible for me to turn it into a trait.
我也找到了这篇关于使用特征的帖子,但由于StructA来自外部货包,我认为我不可能将其转化为特征。
So what would be the correct way to accomplish this in Rust?
那么,在Rust中实现这一目标的正确方法是什么?
更多回答
I think it will always be sub-optimal to answer this question without knowing the context in which you want to use the newly generated struct and why. In my opinion these design decisions highly depend on the specific use-case.
我认为,在不知道要在什么上下文中使用新生成的结构及其原因的情况下回答这个问题总是不太理想的。在我看来,这些设计决策在很大程度上取决于具体的用例。
There is nothing that exactly matches that. There are two concepts that come to mind.
没有任何东西与此完全匹配。我脑海中浮现出两个概念。
Structural composition
struct Person {
age: u8,
}
struct Child {
person: Person,
has_toy: bool,
}
impl Person {
fn new(age: u8) -> Self {
Person { age: age }
}
fn age(&self) -> u8 {
self.age
}
}
impl Child {
fn new(age: u8, has_toy: bool) -> Self {
Child { person: Person::new(age), has_toy: has_toy }
}
fn age(&self) -> u8 {
self.person.age()
}
}
fn main() {
let p = Person::new(42);
let c = Child::new(7, true);
println!("I am {}", p.age());
println!("My child is {}", c.age());
}
You can simply embed one struct into another. The memory layout is nice and compact, but you have to manually delegate all the methods from Person
to Child
or lend out a &Person
.
Traits
trait SayHi {
fn say_hi(&self);
}
struct Person {
age: u8,
}
struct Child {
age: u8,
has_toy: bool,
}
impl SayHi for Person {
fn say_hi(&self) {
println!("Greetings. I am {}", self.age)
}
}
impl SayHi for Child {
fn say_hi(&self) {
if self.has_toy {
println!("I'm only {}, but I have a toy!", self.age)
} else {
println!("I'm only {}, and I don't even have a toy!", self.age)
}
}
}
fn greet<T>(thing: T)
where T: SayHi
{
thing.say_hi()
}
fn main() {
let p = Person { age: 42 };
let c = Child { age: 7, has_toy: true };
greet(p);
greet(c);
}
You can combine these two concepts, of course.
当然,您可以将这两个概念结合起来。
As DK. mentions, you could choose to implement Deref
or DerefMut
. However, I do not agree that these traits should be used in this manner. My argument is akin to the argument that using classical object-oriented inheritance simply for code reuse is the wrong thing. "Favor composition over inheritance" => "favor composition over Deref
". However, I do hold out hope for a language feature that enables succinct delegation, reducing the annoyance of composition.
以DK身份。提到,您可以选择实现Deref或DerefMut。然而,我不同意以这种方式使用这些特征。我的论点类似于仅仅为了代码重用而使用经典的面向对象继承是错误的。“重写作胜于继承”=>“重写作胜于继承”。然而,我确实希望有一种语言功能,能够实现简洁的授权,减少写作的烦扰。
Rust does not have struct inheritance of any kind. If you want StructB
to contain the same fields as StructA
, then you need to use composition.
Ruust没有任何类型的结构继承。如果希望StructB包含与StructA相同的字段,则需要使用组合。
struct StructB {
a: StructA,
// other fields...
}
Also, to clarify, traits are only able to define methods and associated types; they cannot define fields.
另外,要澄清的是,特征只能定义方法和相关类型;它们不能定义字段。
If you want to be able to use a StructB
as a StructA
, you can get some of the way there by implementing the Deref
and DerefMut
traits, which will allow the compiler to implicitly cast pointers to StructB
s to pointers to StructA
s:
如果您希望能够将StructB用作StructA,您可以通过实现Deref和DerefMut特征来实现一些方法,这将允许编译器隐式地将指向StructB的指针转换为指向StructA的指针:
struct StructA;
impl StructA {
fn name(&self) -> &'static str {
"Anna"
}
}
struct StructB {
a: StructA,
// other fields...
}
impl std::ops::Deref for StructB {
type Target = StructA;
fn deref(&self) -> &Self::Target {
&self.a
}
}
fn main() {
let b = StructB { a: StructA };
println!("{}", b.name());
}
Another alternative is to use generics:
另一种选择是使用泛型:
trait IAnimalData {}
struct Animal<D: IAnimalData> {
name: String,
age: i64,
child_data: D,
}
struct Dog {
favorite_toy: String,
}
impl IAnimalData for Dog {}
And then you can implement "child" methods like this, which will only apply to dogs:
然后你可以实现像这样的“孩子”方法,这将只适用于狗:
impl Animal<Dog> {
pub fn bark(&self) -> String {
return "bark!".to_owned();
}
}
And if you want parent methods that apply to all animals, you can implement them like this:
如果您想要应用于所有动物的父方法,可以这样实现它们:
// implements the 'breathe' method for all animals
impl<T: IAnimalData> Animal<T> {
fn breathe() {}
}
The good part is that you don't have to go through the pain of forwarding methods in Dog
to methods in Animal
; you can use them directly inside impl Animal<Dog>
. Also, you can access any fields defined in Animal
from any method of Animal<Dog>
. The bad part is that your inheritance chain is always visible (that is, you will probably never use Dog
in your code, but rather Animal<Dog>
). Also, if the inheritance chain is long, you might get some very silly, long-winded types, like Animal<Dog<Chihuahua>>
. I guess at that point a type alias would be advisable.
好的方面是,您不必经历将Dog中的方法转发到Animal中的方法的痛苦;您可以直接在Iml Animal
中使用它们。此外,您还可以从Animal
的任何方法访问Animal中定义的任何字段。不好的地方是您的继承链总是可见的(也就是说,您可能永远不会在代码中使用Dog,而是Animal
)。此外,如果继承链很长,您可能会得到一些非常愚蠢、冗长的类型,比如Animal
>。我想在这一点上,类型别名将是明智的。
One thing that is good to mention for newcomers to Rust is the way to design your structs/classes/traits. Try to keep your traits small and simple.
对于Rust的新手来说,有一件事值得一提,那就是设计结构/类/特征的方法。试着让你的性格保持小而简单。
And take advantage of possible to use multiples traits for the same class:
并利用对同一类使用多重特征的可能性:
trait Animal {
fn print_name(&self);
}
trait Attack {
fn can_kick(&self) -> bool {
false
}
}
trait Behavior {
fn can_fly(&self) -> bool {
true
}
}
impl Animal for Bird {}
impl Behavior for Bird {}
impl Attack for Bird {}
Rust does not have struct inheritance, but this limitation can be worked around in some cases by using a macro to generate a struct.
Rust没有结构继承,但在某些情况下可以通过使用宏来生成结构来绕过这一限制。
For example, if you want to create Giraffe
and Elephant
structs with common elements,
例如,如果要创建具有公共元素的长颈鹿和大象结构,
macro_rules! AnimalBase {
(#[derive($($derive:meta),*)] $pub:vis struct $name:ident { $($fpub:vis $field:ident : $type:ty,)* }) => {
#[derive($($derive),*)]
$pub struct $name {
// required for all animals
age: i64,
$($fpub $field : $type,)*
}
impl $name {
$pub fn new(age:i64,$($field:$type,)*) -> Self{
Self{
age,
$($field,)*
}
}
}
}
}
And then use it like this:
然后像这样使用它:
AnimalBase! {
#[derive(Debug)]
pub stuct Giraffe {
extra_field_a: String,
}
}
AnimalBase! {
#[derive(Debug)]
pub struct Elephant {
extra_field_b: String,
}
}
// You can create a struct without additional elements, too!
AnimalBase! {
#[derive(Debug)]
pub struct Animal {
}
}
The limitation of the given macro example is, the macro expects #[derive(...)]
on the target struct. You may adapt this and modify it to fit your requirements.
给定宏示例的限制是,宏预期#[派生(...)]在目标结构上。您可以对其进行调整和修改以满足您的要求。
更多回答
This is what I suspected. Thanks for confirming and thanks for the quick answer! For the rest of the noobs out there... do these snippets compile for you? If I don't have the return type for new
I get a compile error.
这就是我所怀疑的。感谢您的确认,并感谢您的快速答复!对于剩下的新手来说...这些代码片段是为您编译的吗?如果我没有new的返回类型,我会得到一个编译错误。
@drebabels no, that's my mistake when transferring it across. I made sure it compiles now :-)
@DreBabels不,这是我在转移它时的错误。我确保它现在可以编译:-)
@Shepmaster: implementing Deref<Target = Person>
and DerefMut
for Child
would go a long way to making composition approach work nicely. Because then you can in most cases treat a Child
as though it were a Person
.
@Shepmaster:实现Deref和DerefMut for Child将大大有助于使组合方法更好地工作。因为在大多数情况下,你可以把一个孩子当作一个人来对待。
Are there any news on this?
这件事有什么消息吗?
@Shepmaster It would be nice to have composition of structs the way golang does it: playground example. It avoids implementing delegates yourselves. Have you pushed an RFC yet ?
@Sepmaster如果有像Golang这样的结构组合就好了:操场的例子。它避免自己实现委托。你推过RFC了吗?
This is a good solution, but the other Rust programmers may laugh at you for naming traits ITraitName
. Just TraitName
is fine. Traits and types aren't part of the same namespace, so there's rarely any confusion (at least, as long as you use dyn
for trait object types).
这是一个很好的解决方案,但其他Rust程序员可能会嘲笑您将特征命名为ITraitName。只要TraitName就行了。特征和类型不是同一名称空间的一部分,因此很少有任何混淆(至少,只要您将dyn用于特征对象类型)。
@trentcl "laugh at you" is a bit harsh. Idiomatic, no, but anyone that's poking fun over someone else's convention needs to self-evaluate.
@trentcl.“嘲笑你”有点刺耳。当然不是,但任何拿别人的惯例开玩笑的人都需要自我评估。
Good example but the code contains typos and is quite incomplete IMO: breathe
must have the &self
argument; Animal<Dog>
and Dog
a new
fn; and the code does not give Dog
the breathe
fn, instead you have to create and handle an Animal<Dog>
object for that. Example: let dog: Animal<Dog> = Animal::new(); dog.breathe(); dog.bark();
这是一个很好的例子,但是代码包含了一些打字错误,并且完全不完整:Breath必须有&self参数;Animal和Dog有一个新的fn;并且代码没有给Dog提供呼吸fn,相反,您必须为此创建和处理一个Animal对象。示例:让Dog:Animal=Animal::new();dog.reapree();dog.bark();
Please provide a little bit more context. What do we get, when we use that macro?
请提供更多的背景信息。当我们使用那个宏时,我们得到了什么?
This gives me an error message on your line 'pub struct Kid':
这在您的行‘pub struct Kid’上给出了一条错误消息:
我是一名优秀的程序员,十分优秀!