Обобщения и трейты отлично подходят для DRY-кода. Они позволяют вам обойтись написанием кода, который работает (почти) везде. К сожалению, в Rust нет встроенной поддержки реализации одного и того же трейта для разных конкретных структур. В качестве обходного пути разработчики Rust придумали такие решения, как макросы, чтобы избежать повторения.

Трейты в Rust

Вот простой пример того, как Rust работает с трейтами:

Что там произошло? Я создал 2 структуры Foo и Bar, и для каждой из них реализовал функцию X(), которая выводила бы их поле a. Это работает, потому что и Foo, и Bar имеют поле a. После этого я также реализовал функцию Y(), которая будет печатать поле b из Bar, поскольку только Bar имеет поле с именем b. Если бы я попробовал тот же трюк с макросом для реализации Y() для Foo, компилятор заорал бы на меня, что Foo не имеет поля b.

Давайте посмотрим, как это сделать в JavaScript

Что здесь случилось? Функциям в JavaScript при вызове передается аргумент context. Он автоматически назначается при вызове функций как X() или Y(), но если вы вызываете их с .call(), вы можете указать свой собственный контекст. Контекст доступен как this внутри вашей функции. Это позволяет нам моделировать struct в Rust как типичный объект JavaScript. Затем implопределение функции для struct просто передает наш объект JavaScript в качестве контекста для функции.

Плюсы и минусы

В конечном счете, все сводится к безопасности и гибкости. Rust гарантирует, что во время компиляции структура поддерживает реализуемую функцию. Используя предыдущий пример, реализация Y() для Foo невозможна, поскольку Foo не содержит поля b. В JavaScript это вполне возможно, Y.call(foo); просто приведет к a is 1 \n b is undefined. С другой стороны, JavaScript не требует «решения», такого как макрос, для реализации одной и той же функции для нескольких структур.

В следующем посте я также расскажу о сходстве переопределения функций между Rust и JavaScript.