Задать вопрос
zigenzoog
@zigenzoog

Как оптимизировать код в rust при помощи trait?

Доброго времени суток!

Я начинающий программист в rust, пытаюсь разобраться, как правильно обыграть trait для f32 и f64.

Есть задача опционально задавать f32 или f64.
В коде закомментированная часть (в ней ошибка) - это я как предполагал должен был выглядеть оптимально код, но видимо я не до конца уловил дженерики, пришлось городить impl'ы для f32 и f64:
mod float_trait {
    pub trait Float {}
    impl Float for f32 {}
    impl Float for f64 {}
}

mod float_struct {
    use crate::float_trait::Float;

    #[derive(Debug)]
    pub struct FloatStruct<T: Float> {
        _a: i8,
        _b: String,
        pub c: T,
    }

    /*impl<T: Float> FloatStruct<T> {
        pub fn new() -> Self {
            Self {
                _a: 2,
                _b: String::from("hi"),
                c: 0.3,  // error[E0308]: mismatched types
            }
        }
    }*/

    impl FloatStruct<f32> {
        pub fn new() -> Self {
            Self {
                _a: 2,
                _b: String::from("hi"),
                c: 0.3,
            }
        }
    }

    impl FloatStruct<f64> {
        #![allow(dead_code)]
        pub fn new() -> Self {
            Self {
                _a: 2,
                _b: String::from("hi"),
                c: 0.3,
            }
        }
    }
}

fn main() {
    let fs = float_struct::FloatStruct::<f32>::new();
    println!("{:#?}", fs.c)
}


Как сделать impl<T: Float> FloatStruct<T> (то что закоменчено), без impl FloatStruct<f32> и impl FloatStruct<f64>?

Ссылка на playground
  • Вопрос задан
  • 143 просмотра
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 3
@deliro
Во-первых, непонятно, как ты хочешь использовать этот трейт
Во-вторых, он у тебя без методов, что делает его целиком бесполезным

Подозреваю, что тебе хочется что-то вроде "сложить два числа, но чтобы можно было и f32 туда и f64". Для этого есть множество стандартных трейтов: Add, Sub, Div, Mul, *Assign, которые и стоит использовать. Например:

fn f<T: Add<Output = T>>(x1: T, x2: T) -> T {
    x1 + x2
}

fn main() {
    println!("{}", f(3.14_f32, 2.7)); // можно f32 + f32
    println!("{}", f(3.14_f64, 2.7)); // а можно f64 + f64
}
Ответ написан
Комментировать
vabka
@vabka Куратор тега Rust
1. Действительно непонятно, для чего и как ты хочешь эти трейты использовать.
2. Кажется, ты пытаешься переизобрести num
3. Ошибка у тебя из-за того что ожидается какой-то неизвестный T, а ты пихаешь туда f64
Тоесть, например, я бы мог захотеть сделать FloatStruct::<f32>::new() но вместо FloatStruct<f32> ты пытаешься вернуть мне FloatStruct<f64>
Ответ написан
Trame2771
@Trame2771
Как я понял, дать гарантии компилятору, для того, чтобы сделать то, что вы хотите, пока нельзя, или сделать это будет очень неочевидно. Гарантии нужны потому что язык безопасный. Возможно когда добавят кастомные маркеры в язык, тогда можно будет чё-то гарантировать. А пока можно пользоваться макросами. Это не так плохо как кажеться. Это не нагружает бинарник, нагружает только компилятор. Причем линейно. Нагрузка на компилятор будет составлять (Количество типов * количество функций). Правда если будет использоваться больше одного типа в структуре, тогда будет применятся комбинаторика для определения нагрузки на компилятор. То есть само приложение будет таким же производительным, правда из-за нагрузки на компилятор, он может перестать немного оптимизировать код. Вот решение чтобы не дублировать код:
mod float_trait {
    pub trait Float {}
    impl Float for f32 {}
    impl Float for f64 {}
}

use crate::float_trait::Float;

mod float_struct {
    use crate::float_trait::Float;

    #[derive(Debug)]
    pub struct FloatStruct<T: Float>
    {
        _a: i8,
        _b: String,
        pub c: T,
    }
    
    macro_rules! impl_float_struct {
        ($($type:ty),*) => {
            $(
                impl FloatStruct<$type> {
                    pub fn new() -> Self {
                        Self {
                            _a: 2,
                            _b: String::from("hi"),
                            c: 0.3,
                        }
                    }
                }
            )*
        }
    }
    
    impl_float_struct![f32, f64];
}

fn main() {
    let fs = float_struct::FloatStruct::<f32>::new();
    println!("{:#?}", fs.c)
}


Другое решение, очевидно нагружающее само приложение, и лёгкое для компилятора
mod float_trait {
    pub trait Float {}
    impl Float for f32 {}
    impl Float for f64 {}
}

mod float_struct {
    use crate::float_trait::Float;

    #[derive(Debug)]
    pub struct FloatStruct<T: std::str::FromStr + Float>
    {
        _a: i8,
        _b: String,
        pub c: T,
    }

    impl<T: std::str::FromStr + Float> FloatStruct<T> {
        pub fn new() -> Self {
            Self {
                _a: 2,
                _b: String::from("hi"),
                c: {
                    let Ok(r) = "0.3".parse::<T>()
                        else {unreachable!()};
                    r
                }
            }
        }
    }
}

fn main() {
    let fs = float_struct::FloatStruct::<f32>::new();
    println!("{:#?}", fs.c)
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы