Автоматически реализовать черты замкнутого типа для новых типов Rust (кортежи с одним полем)

В Rust, кортежные структуры с только одним полем могут быть созданы следующим образом:

struct Centimeters(i32);

Я хочу выполнить базовую арифметику с помощью Centimeters без извлечения своих "внутренних" значений каждый раз с помощью сопоставления с образцом и без реализации функций Add, Sub,... и перегрузок.

Что я хочу сделать:

let a = Centimeters(100);
let b = Centimeters(200);
assert_eq!(a + a, b);

Ответ 1

  Есть ли способ сделать это без извлечения их "внутренних" значений каждый раз с сопоставлением с образцом и без реализации черт Add, Sub,... и операторов перегрузки?

Нет, единственный способ - реализовать черты вручную. Rust не имеет эквивалента расширения Haskell GHC GeneralizedNewtypeDeriving, которое позволяет deriving на типах-обёртках автоматически реализовывать любой тип/черту типа, который реализует обернутый тип (и с текущей настройкой Rust #[derive] как простое преобразование AST, реализовать его наподобие Haskell практически невозможно.)

Чтобы сократить процесс, вы можете использовать макрос:

use std::ops::{Add, Sub};

macro_rules! obvious_impl {
    (impl $trait_: ident for $type_: ident { fn $method: ident }) => {
        impl $trait_<$type_> for $type_ {
            type Output = $type_;

            fn $method(self, $type_(b): $type_) -> $type_ {
                let $type_(a) = self;
                $type_(a.$method(&b))
            }
        }
    }
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub struct Centimeters(i32);

obvious_impl! { impl Add for Centimeters { fn add } }
obvious_impl! { impl Sub for Centimeters { fn sub } }

#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub struct Inches(i32);

obvious_impl! { impl Add for Inches { fn add } }
obvious_impl! { impl Sub for Inches { fn sub } }

fn main() {
    let a = Centimeters(100);
    let b = Centimeters(200);
    let c = Inches(10);
    let d = Inches(20);
    println!("{:?} {:?}", a + b, c + d); // Centimeters(300) Inches(30)
    // error:
    // a + c;
}

манежа

Я эмулировал нормальный синтаксис impl в макросе, чтобы было понятно, что происходит, просто глядя на вызов макроса (т.е. уменьшая необходимость смотреть на определение макроса), а также чтобы поддерживать естественную возможность поиска в Rust: если вы ища черты в Centimeters просто grep для for Centimeters, и вы найдете эти макро-вызовы вместе с обычными impl s.

Если вы часто обращаетесь к содержимому типа Centimeters, вы можете рассмотреть возможность использования правильной структуры с полем для определения оболочки:

struct Centimeters { amt: i32 }

Это позволяет вам писать self.amt вместо того, чтобы выполнять сопоставление с образцом. Вы также можете определить функцию, подобную fn cm(x: i32) -> Centimeters { Centimeters { amt: x } }, называемую как cm(100), чтобы избежать многословия построения полной структуры.

Вы также можете получить доступ к внутренним значениям структуры кортежа, используя синтаксис .0, .1.

Ответ 2

Для этой проблемы я создал ящик для ячеек. Он может извлекать множество черт для структур, элементы которых реализуют их.

Ответ 3

Для Rust версии 1.10.0 мне кажется, что псевдонимы типов идеально подошли бы к описываемой вами ситуации. Они просто дают типу другое имя.

Допустим, все сантиметры - это u32 с. Тогда я мог бы просто использовать код

type Centimeters = u32;

Любая черта, которую имеет u32, Centimeters будет автоматически иметь. Это не исключает возможности добавления Centimeters к Inches. Если вы осторожны, вам не понадобятся разные типы.