News for hackers. Stuff that matters.
Re:Writing Complex Macros in Rust: Reverse Polish Notation by steveklabnik on Thursday February 01, 2018 @10:09AM (#16282324)
C++ templates are used for two different features in Rust: generics, and a more broad "metaprogramming."

The other threads talk a bit about general metaprogramming, so let's talk about the other bit. On the generics side, here's an illustrative example, taken from http://en.cppreference.com/w/cpp/language/function_template

  #include <iostream>
   
  template<typename T>
  void f(T s)
  {
      std::cout << s << '\n';
  }
   
  int main()
  {
      f(7);
  }
Here, the function f will take any type T, and print it out. in this case, 7 is an int, so T is int.

In Rust, here's the same function:

  fn f<T>(s: T) {
      println!("{}", s);
  }

  fn main() {
      f(7);
  }
here, 7 is an i32, so T is i32. But, we get a compilation error in Rust:

  error[E0277]: the trait bound `T: std::fmt::Display` is not satisfied
   --> src/main.rs:2:22
    |
  2 |       println!("{}", s);
    |                      ^ `T` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
    |
    = help: the trait `std::fmt::Display` is not implemented for `T`
    = help: consider adding a `where T: std::fmt::Display` bound
    = note: required by `std::fmt::Display::fmt`

  error: aborting due to previous error
This illuminates a core difference between Rust's generics and C++'s templates: in the C++ case, the code is typechecked after the code is expanded. So it works. However, we don't even need that `main` to cause a compilation error in Rust; it only checks the body of the function. In this case, Rust says "Hey, you accept any T, but you try to print it out. What if a type isn't printable? That wouldn't work!"

The fix is:

  use std::fmt::Display;

  fn f<T: Display>(s: T)

  // or in near-future Rust, a slightly nicer syntax, still needs the 'use' line too:

  fn f(s: impl Display)
This changes the signature to say "we take any type T that implements the Display trait." Now we know that the body of the function works properly.

There are advantages and disadvantages to both approaches. In some senses, the tradeoffs are the same as any duck typed vs not situation: the C++ approach is far more flexible, but the Rust approach has stronger guarantees. We also chose the style we did because we prefer the nicer errors; C++ template errors are notoriously complex, though many say they get used to how to read them over time.

Additionally, neither languages' story is complete here. C++20 has a new feature called "Concepts" that gives you some degree of similar features here, but it's complex and I haven't read the version that was merged into the draft of C++20, so maybe someone else can elaborate on it. Rust is also adding a 'const fn' feature that gives you access to similar tricks to constexpr in C++, as well as more features for our bounded generics.

Re:Writing Complex Macros in Rust: Reverse Polish Notation by pjmlp on Thursday February 01, 2018 @01:54PM (#16284593)
Your example in C++17,

    #include <iostream>
    #include <string>
    #include <type_traits>

    class Display {
        virtual std::string fmt() = 0;
    }; 
    
    template<typename T>
    void f(T s)
    {
        static_assert(std::is_base_of<Display, T>::value, "Error: T must implement Display interface");

        std::cout << s << '\n';
    }
     
    int main()
    {
        f(7);
    }
Gives the following error with clang,

  <source>:12:7: error: static_assert failed "Error: T must implement Display interface"
        static_assert(std::is_base_of<Display, T>::value, "Error: T must implement Display interface");
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  <source>:19:7: note: in instantiation of function template specialization 'f<int>' requested here
        f(7);
        ^
  1 error generated.
  Compiler returned: 1
Of course the downside is having the library author spending extra effort to write those checks.
Re:Writing Complex Macros in Rust: Reverse Polish Notation by steveklabnik on Thursday February 01, 2018 @02:08PM (#16284757)
Totally! There's a lot of code out there that doesn't do this, even if it could. That's also a lot more code, so I imagine people do it less often as well.

Beyond that as well, you won't get an error if you don't use f(7), that is, if your main is empty, which would affect library authors.

Re:Writing Complex Macros in Rust: Reverse Polish Notation by ljrmorgan on Friday February 02, 2018 @09:09AM (#16290566)
This is not really the same though, because the Rust version compiles after the bounds have been added:

  use std::fmt::Display;

  fn f<T: Display>(s: T) {
    println!("{}", s);
  }

  fn main() {
    f(7);
  }
whereas there would be no way to get `f(7)` to compile in your C++ version since you can't make `int` inherit from `Display`. _A_ downside is that the library author has to add these sorts of checks, but for me a bigger downside is that anyone who then wants to use the library has to inherit from the library's types.
Re:Writing Complex Macros in Rust: Reverse Polish Notation by Rusky on Thursday February 01, 2018 @05:38PM (#16286623)
Concepts (lite, the version that is still around) are "one-way." They constrain what types you can use as template arguments, but they do not constrain what templates can do.

Which means the templates themselves can still impose additional requirements based only on their internals, leading to the same bad error messages for users and lack of checking for library authors that we have without concepts.