простой rust generic / функция добавления шаблона

#c #templates #generics #rust

#c #шаблоны #дженерики #Ржавчина

Вопрос:

Итак, я просто изучаю rust — я настроил инструменты и получил все структурно работающие, перекрестную компиляцию, связывание библиотек и так далее… но сейчас я изучаю язык, после нескольких простых функций я теперь хочу написать несколько шаблонов (у меня есть опыт работы с c ). Итак, в c talk я хочу сделать это:

C

 template<typename A, typename B>
auto add(A a, B b)
{
    return a   b;
}

int main() {
    std::cout << "add int int:    " << add(1, 2) << std::endl;
    std::cout << "add int double: " << add(1, 2.3f) << std::endl;
    std::cout << "add int float:  " << add(1, 2.3) << std::endl;
    std::cout << "add char int:   " << add('g', 2.3) << std::endl;
    std::cout << "add str str:    " << add(std::string("bob"), std::string("fred")) << std::endl;
}
  

вывод:

 add int int:    3
add int double: 3.3
add int float:  3.3
add char int:   105.3
add str str:    bobfred
  

Это добавляет к «вещам». Если вы попытаетесь добавить две вещи, которые не реализуют правильный оператор с правильными операндами, вы получите ошибку компиляции (например, добавление строки и int) — довольно просто.

Ржавчина

Теперь к rust (и я совершенно не разбираюсь в этом.

Обычная функция добавления

 pub fn add(a: u64, b: u64) -> u64 {
    return a   b;
}
  

пока все в порядке.

попытка 1 для этого шаблона Я добрался до этой реализации, прослушав ошибки и используя рекомендуемое изменение (используя std::ops::Add<Output = T> я вроде понимаю это, но я действительно не знаю, почему я должен это указывать.

 pub fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T
{
    return a   b;
}


println!("add int int:   {}", add(1, 2));    // ok
println!("add int float: {}", add(1, 2.3));  // fails to compile
  

Это ожидаемо, поскольку я рассказал только об одном типе …. так что теперь попробуйте два типа A, B — но здесь я теряюсь:

 pub fn add<A: std::ops::Add<Output = A>, B: std::ops::Add>(a: A, b: B) -> A
{
    return a   b;
}
  

Ошибка, которую я получаю, это:

 error[E0308]: mismatched types
  --> utils/src/lib.rs:19:16
   |
17 | pub fn add<A: std::ops::Add<Output = A>, B: std::ops::Add>(a: A, b: B) -> A
   |            - expected type parameter     - found type parameter
18 | {
19 |     return a   b;
   |                ^ expected type parameter `A`, found type parameter `B`
   |
   = note: expected type parameter `A`
              found type parameter `B`
   = 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: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
  

Итак, теперь я полностью потерян. Сначала мне, кажется, нужно указать тип возвращаемого значения, но я хочу, чтобы это было выведено для меня. Как мне это сделать? Вероятно, все остальные ошибки связаны с типом возвращаемого значения A — но, похоже, мне нужно что-то указать… любая помощь здесь была бы отличной! — Я хочу заархивировать ту же базовую функциональность шаблона c , что и выше, если это возможно

Ответ №1:

Прежде всего, вам лучше обратиться к документации std::ops::Add first. Есть type Output который вы установили, но есть также параметр шаблона Rhs , который вы также можете установить. Итак, вы можете попробовать:

 pub fn add_to_rhs<A, B>(a: A, b: B) -> B
  where A: std::ops::Add<B, Output = B>
{
    return a   b;
}
  

Там вы объявляете, что значение LHS должно быть добавляемым со значением RHS, создавая тип значения RHS в конце. Но это все равно не будет работать так, как вы ожидали

 println!("add int float: {}", add_to_rhs(1, 2.3)); 
// fails to compile, no implementation for `{integer}   {float}`
  

И это разумно, потому что слабая типизация не для Rust, поскольку она подвержена логическим ошибкам. Каков возможный обходной путь? Практически аналогично тому, что вы сделали бы в нестандартной программе — вы бы явно преобразовали одно из значений:

 pub fn add_to_rhs<A, B>(a: A, b: B) -> B
where A: Into<B>, B: std::ops::Add<Output = B>
{
    return a.into()   b;
}
  

Это не обеспечивает такой же гибкости, как C SFINAE (помните, без слабой типизации), поскольку для добавления любых значений вам нужно будет создать две версии функции; Я показал вариант, в котором значение LHS приводится и добавляется к типу RHS, но вам также нужен другой способ.

И поскольку у нас нет перегрузки в Rust, что становится еще менее удобным, вам придется вызывать разные функции и помнить об этом.

РЕДАКТИРОВАТЬ: я немного поиграл, и мне удалось создать единую функцию для разных сочетаний типов, но ее использование заставляет вас каждый раз указывать возвращаемый тип

 struct Adder<T>(std::marker::PhantomData<T>);
impl<T> Adder<T> {
    fn add<A, B>(a: A, b: B) -> T
      where T: std::ops::Add<Output = T>, A: Into<T>, B: Into<T> {
        return a.into()   b.into();
    }
}


println!("add int int:   {}", Adder::<i32>::add(1, 2));
println!("add int float: {}", Adder::<f64>::add(1, 2.3)); 
println!("add float int: {}", Adder::<f64>::add(2.3, 1));
  

Цель структуры — отделить add параметры шаблона (которые могут быть полностью выведены) от результирующего параметра шаблона (который вам нужно будет указать, иначе возвращаемый тип будет неоднозначным)

Комментарии:

1. это хорошее объяснение — немного грубый синтаксис, но из того, что вы говорите, это потому , что rust использует строго типизированные функции, и поэтому недостатком является просто то, что он менее гибкий — но плюсом является то, что вы получаете различные преимущества безопасности (это мой дополнительный вывод) — это действительно помогает узнать, почему я не могу делать то же самое, что и c , и почему он несколько более подробный …. это правильно?

2. @code_fodder проверьте мою правку. Да, приоритетом Rust является предотвращение появления ошибок во время компиляции. C SFINAE (или утиный ввод, если хотите) чрезвычайно сложно отлаживать, особенно если вы нарушаете некоторые рациональные правила при реализации вещей, например, перегрузку int float , чтобы действовать как int * float

3. хорошо, это действительно имеет большой смысл (ну, мне нужно ознакомиться с синтаксисом там — далеко за пределами моего понимания!) — это похоже на одну из концепций, которые я обдумываю по сравнению с c .. Я привык к тому, что компилятор может многое сделать для меня, но, как вы говорите, это боль, когда вы ошибаетесь! — спасибо