
Rust Traits Deep Dive, Part 2 - chriskrycho
https://newrustacean.com/show_notes/e024/index.html
======
Animats
The text, with examples, is here.[1] Read that, skip the audio.

About operators, he writes "So, to grab one of those earlier examples: when
you write a + b, that's equivalent to calling a.add(b) or b.add(a) or even
Add::add(a, b). The operator is special syntactical sugar for the trait." But
_which one_ of those?

This matters, because it leads to mixed-mode semantics trouble. Who determines
the output type of "a + b"? "a"? "b"? Both? Are there implicit type
conversions? This is where C++ gets into strange interactions between type
converters and generic selection. C++ resolves this with a rule that, when
there are multiple possible instantiations, the selected one must be one notch
"better" than the alternatives.

Python gets into mixed-mode trouble with numpy arrays vs. built-in lists. In
Python, "[2,4] * 2" is [2,4,2,4], while "array(2,4) * 2" is [4,8].

It's a classic problem in language design. If there are no implicit
conversions, users complain about having to write "1.0" instead of "1", or
worse, having to write "1.0L". If there are too many implicit conversions, you
get unexpected semantics. Basically, conversions are there for numbers. Then
somebody decides to generalize them. That sometimes ends badly.

There's nothing here about what Rust does about conversions, but that may be
covered in one of the other many parts of this series.

The good news is that the Rust programming book is finally coming out in just
6 more days. So there's something better to read than this.

[1]
[https://newrustacean.com/show_notes/e024/struct.script](https://newrustacean.com/show_notes/e024/struct.script)

~~~
killercup
> Who determines the output type of "a + b"? "a"? "b"? Both? Are there
> implicit type conversions?

The Add trait is defined in `std::ops::Add` as:

    
    
        pub trait Add<RHS = Self> {
            type Output;
            fn add(self, rhs: RHS) -> Self::Output;
        }
    

(You can find the documentation on <[https://doc.rust-
lang.org/1.26.2/std/ops/trait.Add.html>](https://doc.rust-
lang.org/1.26.2/std/ops/trait.Add.html>))

Both the type of the `self` parameter and the concrete type of `RHS`
determines which trait impl matches. For example: `2 + 2` chooses the `impl
Add<i32> for i32 { type Output = i32; ... }` implementation (literal integers
are of type `i32`). Something like `2 + &2` would choose `impl<'a> Add<&'a
i32> for i32`.

> There's nothing here about what Rust does about conversions, but that may be
> covered in one of the other many parts of this series.

With operators, there are no implicit conversions involved, AFAIK. So all Rust
can do is try and infer the type you want. For literal numbers, it can do so
in a limited fashion. As seen above, if you write `let x = 42;` the type is
inferred as `i32`. If you write `foo(666)` with `fn foo(x: u64)`, the literal
666 is inferred to be meant as `666u64`.

> The good news is that the Rust programming book is finally coming out in
> just 6 more days. So there's something better to read than this.

You can already read it here: [https://doc.rust-lang.org/book/second-
edition/index.html](https://doc.rust-lang.org/book/second-edition/index.html)
:)

~~~
red75prime
> if you write `let x = 42;` the type is inferred as `i32`.

It can be influenced by the x's usage.

    
    
        let x = 42;
        let y = x/2 + 21u32;
    

x is u32 here.

~~~
dorfsmay
Interesting... I'm a newbie at Rust, and expected y to be a float, because of
the division.

But it turns out

    
    
        let x = 11;
        let y = 11/2;
    

==> y = 5 Given how type safety is important to Rust, I expected this to panic
rather than do an automatic cast.

~~~
masklinn
There is no type safety issue, and there is no cast. Div on integral types is
explicitly implementing as an integer/truncating division: [https://doc.rust-
lang.org/src/core/ops/arith.rs.html#436-452](https://doc.rust-
lang.org/src/core/ops/arith.rs.html#436-452)

There would be no type-safety issue or cast if it were implemented as a real
division either, incidentally.

There is room to argue Div should not have been implemented at all on integral
types (as in Haskell where integer division is a separate operation entirely),
but that's a completely different issue.

------
toyg
I've just started working with Rust, coming from a python/java/web background.
Some of the constraints are a bit mind-bending, and the ecosystem is very
immature; but compared to C/C++, it's a dream.

It only needs a few things to achieve world domination, imho: a good
crossplatform GUI toolkit, and a trivial way to interface with higher-level
languages like Python.

~~~
lmm
What is it that you want from Python? Presumably automated memory management -
but if you have GC pauses anywhere in your stack then you can presumably
afford to have them everywhere in your stack. Would a language like OCaml (or
my own favourite, Scala) give you everything you need: Rust-like safety but
Python-like expressiveness?

~~~
toyg
It's just that the Python ecosystem is so much bigger, there are much more
productive libraries for dealing with things that are not performance-
critical. With python you get stuff like QT, nice configuration parsing (rust
has one lib and pretty immature too) and so on, available with zero effort.
Python is now so huge that only a handful of other communities has anything
comparable (Java, PHP, JS and maybe Perl, basically; I would add C# but there
you usually have to pay for stuff).

~~~
lmm
Yeah, availability of libraries is important. That's one thing Scala was quite
clever about bootstrapping: there are native-Scala libraries for a lot of
things these days, but if you need to do something rarer you can very easily
call into a Java library.

------
eslaught
In this example:

    
    
        fn collect<B: FromIterator<Self::Item>>(self) -> B where Self:Sized;
    

I thought this was universal, not existential like the podcast claims. It
would be existential if you instead wrote:

    
    
        fn collect(self) -> impl FromIterator<Self::Item> where Self:Sized;
    

The latter is what got added in 1.26, whereas the former was already available
before that.

And really, you wouldn't want to use the latter in this case because the user
wants to choose the type that's being generated (this is not a case where you
want the callee to choose the type). So the second signature wouldn't make a
whole lot of sense even if this API had been stabilized post-1.26.

~~~
chriskrycho
I may have gotten that wrong! Had a lot of things in flight for this episode
and the universal/existential discussion was a late addition from before I
split it into this and the upcoming episode focused on object safety, dynamic-
vs.-static dispatch, and `impl Trait`.

