Mastering Rust Patterns vol. 1: Rust Newtypes

Wed, 18 Feb 2026 13:41:00 +0000
Rust, Programming Languages, Best Practices

Newtypes are probably the most basic pattern we use in Rust programming language. It is so basic that it's hardly a pattern, however it has some specific use cases where it is very useful. On the other hand, it can also be overused, leading to a bit over-verbose code. Let's explore the pattern and consider what its best applications are.

What are newtypes?

Newtypes are basically single-field structures wrapping some underlying type:

struct Handler(usize);

Here, we created a newtype Handler that holds usize under the hood. That would be a pretty common use case I would use when I have some underlying vector and I don't want to index it with usize - I will create a Handler wrapper instead.

It is important to notice the difference between newtypes and type aliases. The following is not a newtype:

type OtherHandler = usize;

The OtherHandler is just a type alias for usize. The huge difference is that with newtypes we actually create a new type, while the type keyword only creates an alias for a type. Consequence is, that in this example we can use OtherHandler and usize exchangeably, but Handler is completely separate type:

fn takes_handler(handler: Handler) {}
fn takes_usize(handler: usize) {}

fn main() {
    let raw = 0usize;
    let handler = Handler(0);
    // Note that I just assign an `usize` - its the exact same type
    let other_handler = 0usize;
    // Following would not compile, `Handler` is *not* an `usize`
    // let handler2 = 0usize;

    takes_usize(raw);
    // `takes_usize` accepts `OtherHandler`
    takes_usize(other_handler);
    // But doesn't accept `Handler`
    // takes_usize(handler);

    // `takes_handler` accepts `Handler` only
    takes_handler(handler);
    // Passing `usize` or `OtherHandler` fails
    // takes_handler(raw);
    // takes_handler(other_handler);
}

Now understanding a formal difference between type aliases and newtype, let’s consider another aspect of API - the inner field hermetization. Should the only newtype field be public or not? Technically, both cases are valid, but, personally, I prefer to call the newtype types where the type is private. The reasoning here is related to the purpose of newtypes. We use this pattern when technically need information held by an underlying type, but the semantics we use it for are specific to the API and we don't want to allow passing the underlying type directly.

Following this logic – if we make a newtype field public, if the API user has the underlying type, they can pass it directly to the function, which in a way defeats the purpose of newtypes. Here is an example:

struct PubHandler(pub usize);

fn takes_pub_handler(handler: PubHandler) {}

fn main() {
    let raw = 0usize;
    let handler = PubHandler(0);

    takes_pub_handler(handler);
    // Passing `usize` fails
    // takes_pub_handler(raw);
    // But there is nothing stopping us to just create it in-place
    take_pub_handler(PubHandler(raw));
}

This kind of newtype adds a bit of explicit noise which might be what we want, but I think it has mostly different use cases. Because of that, I personally like to call the types with public fields wrapper types while the types with private fields I...