Git Product home page Git Product logo

giftbox's People

Contributors

hypnoseal avatar

Stargazers

 avatar  avatar

Watchers

 avatar

giftbox's Issues

Some tips on generics

/// A simple generics enum example that represents a gift box. The gift box could contain any type
/// of gift or could be empty. `#[derive(Debug)]` macro is used to simplify the path to `println!`.
#[derive(Debug)]
enum GiftBox<T> {
    Gifts(T),
    Empty
}

So to clarify this declaration. When you say a gift box could contain "any" type, that is true depending on what you mean by any. Generics associate another type (T here) with a type (GiftBox) to create a combined type. In this case you have to choose 1 specific T value each time you have a variable that stores a GiftBox (not entirely true, Ill comment on generic functions later). Something which might help clarify is the most common/well known example of generics are lists (aka vector in rust). Rust has:

struct Vec<T> {...}

but the T value is only "any" type at the moment of declaration. The moment you create a vector, you have to decide what it is a vector of, so that the memory for it can be layed out. In the same way, GiftBox needs to decide what it is a GiftBox "of" the moment it is layed out in memory. You cannot create an "empty box, which you can decide what you want it to be a box of later", as the compiler wouldnt know how to lay out the memory.

/// In this example an empty gift box is declared and no gift box is declared with gifts. This is
/// doomed to compilation failure because the compiler does not know what type the gifts could be.

The reason for the compilation failure is because it needs to know how to lay out the memory. You might be surprised to find that you wont actually need to have the T value be left undetermined.

fn main() {
    // Declare an empty gift box called `empty_box`.
    let empty_box = GiftBox::Empty;

    // Attempt to `println!` to see what the gift box contains. However, this will not compile! An
    // error will be displayed stating "cannot infer type for type parameter `T` declared on the
    // enum `GiftBox`".
    println!("The gift box contains: {:?}", empty_box);
}

When you declare the empty box, if you dont plan on putting anything in the box, then you can just associate the empty type with the GiftBox by adding the type annotation to the empty box. let empty_box: GiftBox<()> = GiftBox::Empty; That tells the compiler "ah, so for the empty_box variable, the T associated with giftbox in that instance is the empty type". If you wanted to assign a gift value to that variable, you would then only be allowed to put in an empty type instance empty_box = GiftBox::Gifts(())/. This would not compile: empty_box = GiftBox::Gifts(true) because the empty_box type doesnt have a T of boolean

/// If the purpose of the `GiftBox<T>` enum is to allow any sort of type to be within
/// `GiftBox::Gifts`, how do we best accomplish this?
///
/// - Do we have to always hint to the type of `T` if we only declare `GiftBox::Empty`?
/// - Do we have to be explicit on the types that `T` can be?
/// - Is this where turbofish is supposed to be used?!
fn what_do() {
    todo!("Need to figure out how to make this a fully functional generic!");
}

If you would like to accept a GiftBox, but this particular function does not care what kind of gift is in the gift box, then you can make the function itself generic. Example:

fn open_gift_or_panic<T>(gift: GiftBox<T>) -> T {
     return match gift {
        GiftBox::Gifts(the_gift) => the_gift,
        GiftBox::Empty => panic!("I thought I was going to get a gift! D: "),
    }
}

Here the on the function lets the function work for any T because the code works regardless of what the type in the gift is, it only needs to return the type which is whatever type was in the gift box.

  • Do we have to always hint to the type of T if we only declare GiftBox::Empty?
    Yes (sorta). The compiler must always know what T is for every variable. If the value is a concrete instance of GiftBox (as in your empty_box example) then it must know the T, or in the case of the generic function, it passes the responsibility of figuring out what the T is on to the caller of the function.

  • Do we have to be explicit on the types that T can be?
    You can choose if you want to constrain what types are allowed to be for T. For example, lets say we had a function where we wanted to make a comparison between the gift and a number. In that case the function would only work for types T which can be compared with a number. Notice the type constraint : PartialOrd<u8> (meaning can be compared with) in the generic declaration.

fn open_big_gifts_or_panic<T: PartialOrd<u8>>(gift: GiftBox<T>) -> T {
     return match gift {
        GiftBox::Gifts(the_gift) => 
             if (the_gift > 10) { the_gift } else { panic!("I only open big gifts.") },
        GiftBox::Empty => panic!("I thought I was going to get a gift! D: "),
    }
}

Type constraints like this can also be added to the enum itself so that the giftbox can only hold types which meet certain crtieria (aka implements certain traits).

  • Is this where turbofish is supposed to be used?!
    Normally, most generics in Rust the compiler can "figure out". So for example, in the code I suggested above let empty_box: GiftBox<()> = GiftBox::Empty; we add a type annotation onto empty_box which lets the compiler know "hmm, I see () where the T goes for this isntance, so when it says GiftBox::Empty, that must be a GiftBox<()> empty value" even though you never had to put the () directly on the GiftBox::Empty. Turbofish is a way to tell it explicitly what T to use if it cant just "figure it out". So if we dont want to add the type annotation onto empty_box, we can alternatively tell it what T to use with let empty_box = GiftBox::<()>::Empty (where the "::<()>" I added is the turbofish). That just tells it what T to use at the call site.

  • (From the readme) How does Option accomplish this?
    Option works just like your GiftBox does actually. They are basically identical. Option also must have a specified T associated with every instance, but as talked about above the compiler is usually able to "figure it out" so that you dont have to type the T out.

Note: Fair warning here, I wrote this while I was in a meeting so didnt actually compile any of my stuff. Hopefully I remembered the syntax all right.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.