Допустим, у нас есть структура игрового движка:
struct GameEngine {
    ... тут всякие кроссплатформенные поля вроде id OpenGL программ и т. д. ...
}
impl GameEngine {
    fn new() -> GameEngine {
        GameEngine {
            ...
        }
    }
    fn render(&mut self) {
        ...
    }
    ... какие-то ещё методы для обработки всяких событий ...
}
Дальше, мы хотим реализовать дочерний класс, который бы реализовывал движок под конкретную платформу. В Rust принято использовать композицию вместо наследования, окей:
struct SDLGameEngine {
    GameEngine engine;
    ... Всякие платформозависимые переменные ...
    window: sdl2::video::Window
}
impl SDLGameEngine {
    fn new() -> SDLGameEngine {
        SDLGameEngine {
            engine: GameEngine::new(),
            ...
        }
    }
    fn main_loop(&mut self) {
        loop {
            self.handle_events();
            self.engine.render();
        }
    }
    ...
}
Всё хорошо до тех пор, пока SDLGameEngine полностью рулит GameEngine - вызывает всякие разные методы в ответ на всякие разные события (рендер, нажатие клавиш, движения мыши), а GameEngine меняет только своё внутреннее состояние и дёргает вызовы OpenGL.
Однако в один прекрасный момент нам захотелось показывать FPS в заголовке окна. При этом логика рассчёта FPS, очевидно, не зависит от платформы, а вот процедура смены заголовка окна явно зависит. Так что теперь GameEngine должен повлиять на SDLGameEngine, а не наоборот.
Окей, делаем трейт:
trait GameEnginePlatform {
    fn set_title(&mut self, title: &str);
}
А теперь у нас есть три варианта:
1. Реализуем трейт для SDLGameEngine (ведь в нём лежит реальное окно). Но не можем передать его в конструктор GameEngine, потому что в момент создания GameEngine SDLGameEngine ещё не существует. В свою очередь SDLGameEngine не может быть создан без экземпляра GameEngine. Начинать использовать указатели как-то глупо с учётом того, что структура очень простая (кто кем владеет) и у нас нет проблем с управлением памятью.
2. Реализуем трейт для SDLGameEngine (ведь в нём лежит реальное окно). Не будем передавать его в конструктор, добавим аргумент к функции render:
fn render(&mut self, platform: &mut dyn GameEnginePlatform) {
    ...
    platform.set_title(...);
}
...
fn main_loop(&mut self) {
    loop {
        self.handle_events();
        self.engine.render(self);
    }
}
Получаем ошибку заимствования:
   |         self.engine.render(self);
   |         ^^^^^^^^^^^^^^^^^^^----^
   |         |                  |
   |         |                  first mutable borrow occurs here
   |         second mutable borrow occurs here
   |         first borrow later captured here by trait object
В целом логично.
3. Дробим SDLGameEngine на две части. Одна основная, а другая управляет окном и реализует трейт для смены заголовка. Передаём её в конструктор GameEngine. И... Лишаемся возможности управлять окном из SDLGameEngine. А нам это хочется, ведь часть операций над окном платформозависимая.
4. Дробим SDLGameEngine на две части. Одна основная, а другая управляет окном и реализует трейт для смены заголовка. Храним её внутри SDLGameEngine, передаём ссылку в метод render. В свободное от вызова этого метода время можем управлять окном сами.
Получается, что в Rust единственный способ связи родителя класса с потомком (в терминах ООП) это вариант 4. Или есть альтернативы? Мне не очень нравится, что GameEngine теперь может обратиться к SDLGameEngine исключительно в специальных методах, которые принимают специальные параметры, а SDLGameEngine пришлось распилить на две части. В данном примере всё очень примитивно, однако в более сложной программе, мне кажется, это может стать проблемой.
Бонусный вопрос: я правильно понимаю, что trait это фактически vtable отделённый от самого объекта и в Rust каждая структура может иметь множество vtable? Как это вообще реализовано на низком уровне?











