Some questions about Rust casts

In rust, I try to implementation Into trait for my types. calling into() for these types happens on the critical performance path, so I want to know Into is zero-cost abstraction and it happen at compile time?

On a release build with inlining, .into() costs only whatever the actual operation costs. So u32 as Into<u64> will be pretty cheap since it's just zero-extending a single number, but Box<i32> as Into<Rc<i32>> will be more expensive since it has to reallocate the underlying buffer. It should practically never be more expensive than just writing the operation out manually.

Also, it's generally recommended to implement From instead of Into on your types. Doing so will automatically generate an Into implementation, and also allow you to write MyType::from(value) to avoid extra type annotations.

8 Likes

Yes, pretty much.

No, that's not possible in general. Value conversions can't happen "at compile time", because they depend on the actual runtime value. So asking if they happen at compile time doesn't make much sense.

If you are interested in whether method calls will be inlined: you can trust the compiler with that. Trivial conversions will almost always be inlined.

You should do benchmarks. In general, you can expect that explicit into calls will be inlined in release build and will remain in debug builds (which, by the way, has potential to tank debug build performance). But as usual with optimizations, no one can give you hard guarantees, so you should check for yourself. In general, if the function body is sufficiently small and simple, or if a functions is called only in a single place, then LLVM will inline it. You may also consider adding #[inline] attribute to the implementation:

impl From<Foo> for Bar {
    #[inline]
    fn from(val: Foo) -> Bar { ... }
}

This will allow to inline the implementation if you are calling it from a different crate. Note that you generally don't need it in a single crate, or if the implementation has generic parameters (generics can always be inline across crates). Also note that #[inline] can negatively affect code bloat and compile times, so it is not recommended to use it without specific benchmarks which prove its worth.

You may also consider using LTO to maximize the possibility of method inlining.

1 Like

It is irrelevant whether the function is called Into::into or Foo::bar. What matters is what the function does exactly. Without seeing the implementation it is impossible to say anything about its performance.

2 Likes

Generic answer: it depends on the optimizer, so the only way to know for sure is to run a profiler on your code and find out. (And you might want full LTO.)

Specific answer: trivial conversions might even disappear completely, depending whether LLVM can just widen the operations for the whole function, or maybe if it's using platforms with bigger registers where extend/truncate is just using different register names. Complicated conversions -- like &str to String -- won't disappear, but using Into for it is still a "zero cost abstraction" in the sense that writing them a different way (such as with From instead) won't (in release mode) perform better.

2 Likes

@algebra2k

We’re you thinking that Into is a form of type casting?

or, perhaps thinking (correctly) that there is no “lookup” cost using the trait due to monomorphization that takes place during compilation?