Обобщения и трейты отлично подходят для 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.