Я пытаюсь научиться создавать в Rust функцию, которая будет складывать два числа независимо от их типов (два целых числа, два числа с плавающей точкой, число с плавающей запятой и целое число или целое число и число с плавающей запятой). Я пытаюсь добиться этого, используя общие параметры в сигнатуре моей функции. Ниже приведен пример кода Rust:
use num_traits::Num;
fn main() {
println!("{}", sum_two_numbers(5, 6));
}
fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
num1 + num2
}
Вышеупомянутое не компилируется. Я получаю следующую ошибку:
error[E0308]: mismatched types
--> src\main.rs:13:12
|
12 | fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
| - - found type parameter
| |
| expected type parameter
13 | num1 + num2
| ---- ^^^^ expected type parameter `T`, found type parameter `U`
| |
| expected because this is `T`
|
= note: expected type parameter `T`
found type parameter `U`
= note: a type parameter was expected, but a different one was found; you might be missing a type parameter
or trait bound
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
error[E0308]: mismatched types
--> src\main.rs:13:5
|
12 | fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
| - - expected type parameter - expected `U` because of return type
| |
| found type parameter
13 | num1 + num2
| ^^^^^^^^^^^ expected type parameter `U`, found type parameter `T`
|
= note: expected type parameter `U`
found type parameter `T`
= note: a type parameter was expected, but a different one was found; you might be missing a type parameter
or trait bound
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
For more information about this error, try `rustc --explain E0308`.
error: could not compile `generics-for-stackoverflow-question` (bin "generics-for-stackoverflow-question") due
to 2 previous errors
Я знаю, что типы i32 можно преобразовать в f64 следующим образом:
5 as f64;
Я надеялся, что компилятор сможет понять, что я пытаюсь сложить два разных типа чисел, и поможет мне их преобразовать. Очевидно, что это не работает.
Обратите внимание, что черта Num взята из внешнего ящика, который я добавил в свой проект (ящик — num_traits).
Ранее я написал функцию таким образом, чтобы она компилировалась, как показано ниже:
use num_traits::Num;
fn main() {
println!("{}", sum_two_numbers(5, 6));
}
fn sum_two_numbers<T: Num>(num1: T, num2: T) -> T{
num1 + num2
}
Это позаботится о добавлении целого числа к целому числу или числа с плавающей запятой к числу с плавающей запятой, но не добавления числа с плавающей запятой к целому числу или наоборот.
Дополнения между произвольными типами поддерживаются в Rust для тех типов, которые явно реализуют эти комбинации типов. Однако для чисел существуют только реализации сложения чисел одного типа. Поэтому нам нужно убедиться, что оба числа имеют один и тот же тип. Мы можем выполнить преобразование автоматически для пользователя, учитывая, что преобразование из одного типа в другой определено. Признак From
реализован для типов, поддерживающих преобразование из другого типа в этот тип.
Так как же нам заставить компилятор позволить вам выполнить это преобразование? Предположим, мы сначала хотим преобразовать тип первого числа (T
) в тип второго числа (U
). Прежде чем выполнять сложение, нам нужно потребовать, чтобы тип U
поддерживал автоматическое преобразование из типа T
. Мы делаем это, требуя от U
реализовать не только Num
, но и From<T>
. После этого мы получим доступ к этим дополнительным функциям и сможем преобразовать num1
в U
. Тогда дополнение будет работать нормально.
fn sum_two_numbers<T: Num, U: Num + From<T>>(num1: T, num2: U) -> U{
U::from(num1) + num2
}
Обратите внимание, что обычно для числовых типов определяется только преобразование, не связанное с риском потери точности, поэтому вы можете автоматически конвертировать, например, из от u8 до u16, но не наоборот. Итак, это отлично работает:
// 5u8 is first converted to u16, and then added to 6u16
println!("{}", sum_two_numbers(5u8, 6u16));
Но это не компилируется:
println!("{}", sum_two_numbers(5u16, 6u8));
| --------------- ^^^ the trait `From<u16>` is not implemented for `u8`
| |
| required by a bound introduced by this call
Если вы все равно хотите попробовать эту операцию, вместо этого вы можете использовать поддержку условного преобразования с признаком TryFrom
. Он пытается выполнить преобразование, но в случае неудачи выдает ошибку времени выполнения:
fn sum_two_numbers2<T: Num, U: Num + TryFrom<T>>(num1: T, num2: U) -> Result<U, U::Error> {
Ok(U::try_from(num1)? + num2)
}
Оператор вопросительного знака заставляет функцию возвращать ошибку в случае сбоя преобразования. В противном случае результат сложения оборачивается Ok()
результатом, что указывает на успех.
Предыдущие примеры теперь компилируются и работают нормально (здесь я также использую оператор вопросительного знака, чтобы функция main
возвращала ошибку в случае сбоя преобразования):
fn main() -> Result<(), Box<dyn Error>> {
println!("{}", sum_two_numbers2(5u16, 6u8)?);
println!("{}", sum_two_numbers2(5u8, 6u16)?);
Ok(())
}
// prints "11" twice
В случае, если число слишком велико, выручит:
fn main() -> Result<(), Box<dyn Error>> {
println!("{}", sum_two_numbers2(1000u16, 6u8)?);
Ok(())
}
// prints: Error: TryFromIntError(())
Аналогично, ваш пример с целым числом для числа с плавающей запятой работает с обеими моими реализациями, когда число с плавающей запятой является последним аргументом:
println!("{}", sum_two_numbers(1000u16, 6.3f32));
println!("{}", sum_two_numbers2(1000u16, 6.3f32)?);
// prints: 1006.3 twice
Однако обратный путь не удастся, поскольку тип результата всегда будет типом второго аргумента, как было предложено вашими попытками в вопросе, и даже условное преобразование из числа с плавающей запятой в целое число не поддерживается. Вы, конечно, можете реализовать свою собственную особенность MyFrom
для всех типов чисел, между которыми вам нужно конвертировать, и поддерживать больше опций с любыми побочными эффектами, которые они подразумевают.
Теперь, добавляя обработку ошибок, у нас может возникнуть соблазн также поддержать проверку на переполнение и возврат ошибки в этом случае (например, вы можете сделать 200u8.checked_add(200u8)
и получить None
, чтобы указать на переполнение), но, увы, крейт num_traits
, похоже, не поддерживает это.