Приведение к универсальному типу

У меня есть вопрос новичка о дженериках в Rust (версия 1.0).

Допустим, я пишу обобщенную функцию для деления. Не берите в голову полезность такой функции; Это простая функция, чтобы держать этот вопрос простым.

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    a / b
}

fn main() {
    println!("{}", divide(42, 18))
}

Эта программа не компилируется.

src/main.rs:2:5: 2:10 error: mismatched types:
 expected `T`,
    found `<T as core::ops::Div>::Output`
(expected type parameter,
    found associated type) [E0308]
src/main.rs:2     a / b
                  ^~~~~

Я понимаю, что ошибка компилятора говорит мне, что результатом операции деления является тип Outputне Tи я вижу Output введите в стандартной документации библиотеки.

Как мне конвертировать из Output в T? Я пытаюсь использовать as транслировать.

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    (a / b) as T
}

fn main() {
    println!("{}", divide(42, 18))
}

Это вызывает другую ошибку компилятора.

src/main.rs:2:5: 2:17 error: non-scalar cast: `<T as core::ops::Div>::Output` as `T`
src/main.rs:2     (a / b) as T
                  ^~~~~~~~~~~~

У меня нет идей сделать эту работу, и я понимаю, что мне не хватает понимания чего-то фундаментального в этом языке, но я даже не знаю, что искать, чтобы сделать эту работу. Помогите?

2 ответа

Решение

Вы просто должны указать T::Output в качестве типа возврата функции:

fn divide<T: std::ops::Div>(a: T, b: T) -> T::Output {
    a / b
}

Изменить, чтобы добавить больше объяснений, почему вы не можете выполнять приведение внутри функции

Когда вы НАХОДИТЕСЬ за разделение общей функции, компилятор еще не знает, что вы можете привести T в T::OutputТаким образом, приведение неверно. Это универсальные типы, они могут быть любыми, как компилятор знает, что вы можете привести T в T::Output?

a / b производит что-то типа T::OutputТаким образом, в решении выше не приведен, T::Output это просто правильный тип.

Отредактируйте, чтобы добавить другое возможное решение, используя std::convert::From

Наиболее (я думаю) общая реализация - это когда вы знаете, что T::Output до Т возможно. Вы можете связать T реализовать From за T::Output, Это полный пример:

use std::ops::Div;
use std::convert::From;

fn divide<T: Div>(a: T, b: T) -> T
    where T: From<<T as Div>::Output>
{
    T::from(a / b)
}

#[derive(Debug)]
struct Bip(u32);

impl Div for Bip {
    type Output = f32;

    fn div(self, rhs: Bip) -> f32 {
        (self.0 / rhs.0) as f32
    }
}

impl From<f32> for Bip {
    fn from(value: f32) -> Self {
        Bip(value as u32)
    }
}

fn main() {
    println!("{:?}", divide(12, 4));
    println!("{:?}", divide(Bip(12), Bip(4)));
}

Ответ Андреаса объясняет, почему приведение невозможно, и что создание функции как можно более универсальной решает эту проблему.

Однако это не единственное решение. Rust также поддерживает возможность ограничения связанных типов (Output здесь) общей функции.

Альтернативой будет:

use std::ops::Div;

fn divide<T>(a: T, b: T) -> T
    where T: Div<Output = T>
{
    a / b
}

<Output = T> бит инструктирует компилятор принимать только в этой функции типы T для которого реализация Div имеет Output равно T, Это, очевидно, более ограничивает, но позволяет гарантировать, что результат divide имеет тип T,

Другие вопросы по тегам