Newtype clarification needed

A commonly used strategy in Rust is to use the newtype pattern (i.e. wrap some less-specific type in a more-specific type) which works great because its runtime cost is effectively zero.

However the usual examples of newtyping online all seem to focus on using a tuple struct wrapper with a single member e.g. struct Newtype(usize);.
But is a non-tuple struct with a single member e.g. struct Foo { primitive: usize } also a newtype in the sense of zero runtime overhead?

Not as far as I know. Why would a non-tuple struct have any more overhead than a tuple struct?

The newtype pattern doesn't depend on any special compiler optimizations, nor is it restricted to any particular subset of types. Really, a "newtype" is just a type whose sole purpose is to have similar or identical content/behavior to some other type. All operations on NewType/Foo are going to compile down to operations on a usize anyway, so any decent optimizer should be able to get rid of NewType/Foo entirely.

(newtypes do create an interesting ABI issue for FFI code, which is solved by #[repr(transparent)], but that never mattered for performance afaik)

To my knowledge, struct Newtype(T) will behave exactly like struct Newtype { t: T } at runtime. They optimize equally well as each other and as T by itself.

2 Likes

Once you hit codegen, there's no longer a difference between the two kinds of structs, the same way there's no difference between structs with different field names.

2 Likes

That's all I needed to know, thanks!

FTR, I agree: conceptually there's no reason for Rust to treat Foo(usize) different from Foo { pos: usize }.
But it's not obvious from any documentation I found that this is indeed the case in practice i.e. that rustc actually does treat them the same.