Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I'm re-writing existing code of mine in Rust 1.6 and I've found it very convenient in the source language to label a type by typedef. For example, in my card game I have a rank value in F# defined as:

type Rank = uint8
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
665 views
Welcome To Ask or Share your Answers For Others

1 Answer

From The Rust Programming Language section titled Creating Type Synonyms with Type Aliases:

Rust provides the ability to declare a type alias to give an existing type another name. For this we use the type keyword. For example, we can create the alias Kilometers to i32 like so:

type Kilometers = i32;

Now, the alias Kilometers is a synonym for i32; [...], Kilometers is not a separate, new type. Values that have the type Kilometers will be treated the same as values of type i32:

type Kilometers = i32;

let x: i32 = 5;
let y: Kilometers = 5;

println!("x + y = {}", x + y);

There's more that you should read, but this answers the question.


As a bit of editorial, I don't think that a type alias is a great fit in a lot of places that people use them. Assuming that your Rank type represents something to do with a deck of cards, I'd suggest either an enum or a newtype. The reason is that with a type alias you can do something like this:

let rank: Rank = 100;

Which is nonsensical for a typical deck of cards. An enum is a restricted set. This means you can never create an invalid Rank:

enum Rank {
    One, Two, Three, Four, Five,
    Six, Seven, Eight, Nine, Ten,
    Jack, Queen, King, Ace,
}

impl Rank {
    fn from_value(v: u8) -> Result<Rank, ()> {
        use Rank::*;

        let r = match v {
            1 => One,
            2 => Two,
            // ...
            _ => return Err(()),
        };
        Ok(r)
    }

    fn value(&self) -> u8 {
        use Rank::*;

        match *self {
            One => 1,
            Two => 2,
            // ...
        }
    }
}

A newtype is just a wrapper type. It consumes no extra space compared to the wrapped type, it just provides an actual new type that lets you implement methods that can restrict to valid values. It's possible to create invalid values, but only within your own code, not all client code:

struct Rank(u8);

impl Rank {
    fn from_value(v: u8) -> Result<Rank, ()> {
        if v >= 1 && v <= 14 {
            Ok(Rank(v))
        } else {
            Err(())
        }
    }

    fn value(&self) -> u8 {
        self.0
    }
}

I tend to use type aliases as quick placeholders of types. While writing the above examples, I actually wrote:

type Error = ();

And returned a Result<Rank, Error>, but then thought that would be confusing. :-)

The other case I use them is to shorten a larger type that I don't want to hide. This happens with types like iterators or Results, which you can see in the standard library. Something like:

type CardResult<T> = Result<T, Error>;

fn foo() -> CardResult<String> {
    // ..
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...