Возможно ли, чтобы одна структура расширила существующую структуру, сохранив все поля?

Используя rust 1.2.0

Проблема

Я все еще изучаю Rust (исходя из фона Javascript) и пытаюсь выяснить, возможно ли для одной структуры StructB расширить существующую структуру StructA, чтобы StructB все поля, определенные на StructA.

В Javascript (синтаксис 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;
    }
}

Ограничения

  • StructA - это внешний cargo пакет, который я не контролирую.

Текущий прогресс

Я нашел этот пост в блоге по одинарному наследованию, который звучит как именно то, что мне нужно.

Но попытка его реализации привела к появлению этого сообщения об ошибке error: virtual structs have been removed from the language. Некоторые поиски позже, и я узнал, что он был реализован, а затем удален в RFC-341 довольно быстро.

Также найден этот поток об использовании признаков, но так как StructA из внешнего пакета груза, я не думаю, что это возможно для меня превратить в черту.

Итак, каков был бы правильный способ сделать это в Rust?

Ответ 1

Нет ничего, что бы точно соответствовало этому. Есть два понятия, которые приходят на ум.

  • Структурная композиция

    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());
    }
    

    Вы можете просто вставить одну структуру в другую. Макет памяти хорош и компактен, но вы должны вручную делегировать все методы от Person до Child или одолжить &Person.

  • Черты характера

    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);
    }
    

Вы можете объединить эти две концепции, конечно.


Как DK. упоминает, вы можете выбрать Deref или DerefMut. Однако я не согласен с тем, что эти черты должны использоваться таким образом. Мой аргумент сродни аргументу о том, что использование классического объектно-ориентированного наследования просто для повторного использования кода - это неправильная вещь. "Использовать композицию над наследованием" = > "одобрить композицию над Deref". Тем не менее, я действительно надеюсь на языковую функцию, которая позволяет лаконичную делегацию, уменьшая раздражение композиции.

Ответ 2

Rust не имеет наследования структуры любого типа. Если вы хотите, чтобы StructB содержал те же поля, что и StructA, вам нужно использовать композицию.

struct StructB {
    a: StructA,
    // other fields...
}

Кроме того, чтобы уточнить, черты могут определять только методы и связанные с ними типы; они не могут определять поля.

Если вы хотите использовать StructB как StructA, вы можете получить часть этого пути, выполнив Deref и DerefMut, что позволит компилятору непрямо указывать указатели на StructB на указатели на StructA s:

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());
}

Ответ 3

Реализация Deref для умных указателей, которые мы можем получить доступ к данным позади, вроде как наследовать.

struct Person {
  gender: &'static str,
  age:    &'static i32
}

struct Child(Person);

impl std::ops::Deref for Child {
  type Target = Person;
  fn deref(&self) -> &Self -> &Self::Target {
    &self.0
  }
}

fn main() {
  let child = Child(Person{
    gender: 'male',
    age:     6_i32
  });

  println!("your child is {}, {} years old.", child.gender, child.age);
}