Skip to content

Instantly share code, notes, and snippets.

@steveklabnik
Last active August 29, 2015 14:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save steveklabnik/7916d6256a6517e8282e to your computer and use it in GitHub Desktop.
Save steveklabnik/7916d6256a6517e8282e to your computer and use it in GitHub Desktop.
Rust first project

Help the docs!

EDIT: If you read the gist before, I've updated it with my latest version. I'm pretty sure I took care of everyone's comments, thanks so much!

So! The new tutorial will be focused on building several small projects in Rust. This example is the first one: a classic 'guessing game.' This was one of the first programs I wrote when I first learned C. 😄

I'd like the feedback of the community before I actually start writing the guide. So this code will be the final code of the first real example Rust programmers see. So I want it to be good. I don't claim this code is good, I just worked something out real quick. Oh, and this is tracking master.

The idea is that I will slowly build from hello world to this final code in steps, introducing one concept at a time. Here are the concepts I'd like a Rust programmer to understand by the time they're done:

Concepts

  • If
  • Functions
    • return (wrt semicolons)
  • comments
  • Testing
  • attributes
  • stability markers
  • Crates and Modules
  • visibility
  • Compound Data Types
  • Tuples
  • Structs
  • Enums
  • Match
  • Looping
  • for
  • while
  • loop
  • break/continue
  • iterators
use std::io;
use std::rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = std::rand::task_rng().gen_range(1i, 101);
println!("Secret number is {}", secret_number);
let mut guesses: int = 0;
let mut reader = io::stdin();
loop {
println!("Please input guess number {}", guesses + 1);
let input = reader.read_line().ok().expect("Failed to read line");
let input_num = from_str::<int>(input.as_slice().trim());
let num = match input_num {
Some(num) => num,
None => {
println!("Please input a number.");
continue;
}
};
println!("You guessed: {}", num);
guesses += 1;
match num.cmp(&secret_number) {
Less => println!("Too small!"),
Greater => println!("Too big!"),
Equal => {
println!("You win!");
break;
},
}
}
println!("You took {} guesses!", guesses);
}
@nightpool
Copy link

I don't know how this is going to be documented, but I would make extra sure to highlight the differences between .unwrap() and the match block.

Otherwise it looks great! Clear, concise and fun. Great job Steve.

@alan-andrade
Copy link

This is amazing! Thanks Steve : )

I was wondering if it'd be worth talking about lifetimes or the lifetimes guide would cover it ?

@jahan-paisley
Copy link

Things which comes to my mind as a newbie when reading the code, just thinking loud:
There is no declaration for generate_secret_number before main 👍, comparing to C.
There is no class, just two simple function with readable signatures, while main has no return type.
What's the purpose of as_slice is unknown to me. unwrap too as @nightpool mentioned.
Pattern matching and immutability, new concept for a developer which comes from a non-functional background.
I think namespace including and :: operator in different contexts like in line 20, needs description.
And last thing is those values at the left side of string pattern matching, Less, Greater and Equal, are they same old constants or ...?

Thanks!

@the-undefined
Copy link

This is the first Rust that I have seen, I purposefully kept my eyes shut to rust since you mentioned you were writing docs, so I could learn from your docs.... that sounds crazier written down then it did in my head :-/

For the number game: it's simple enough that I could follow along, made sense and peeked my interest, so thumbs up! 👍

If it helps what I found interesting was:

  • ("Please input guess #{}", guesses + 1) - interpolation? (probably the wrong terminology), took me a couple of glances to grok the syntax but that's because I'm used to ruby
  • let mut guesses: int = 0; - sweet syntax for assignment
  • from_str::<int> - type casting?
  • input.as_slice().trim_right() - very descriptive and readable method chaining 😃
  • match - now that looks like a neato feature! Is it pattern matching per se? or more akin to the switch statement?

The only negative thing that ran through my head was: it's probably still a pain to install and setup being so new to try this out locally. Completely unfounded I'm sure, I just noticed that it prevented me from doing it though.

Let me know if you'd be interested any other kind of feedback, hope that helps. Good luck!

@Ryman
Copy link

Ryman commented Jun 27, 2014

It's a good point that having "... #{}" in the first println with args might throw off rubyists.

@steveklabnik
Copy link
Author

Great suggestions, everyone! Thanks

@nightpool: yes, absolutely. very important.

@alan-andrade lifetimes will be near the end, this is the very beginning 😉

@jzinedine and @the-undefined, thank you! This information will help me explain it better in the text

@Ryman didn't even think of that: good call.

@bachm
Copy link

bachm commented Jun 27, 2014

gen_range will return a number in the range [low, high). There's an opportunity to demonstrate how ranges work by pointing this behaviour out.

@cgaebel
Copy link

cgaebel commented Jun 27, 2014

I think using .unwrap should be replaced with .expect. It's better style and gives nicer error messages. It's also a good example of the good user-friendly error checking.

@steveklabnik
Copy link
Author

@bachm yes, I screwed it up. Heh. What's funny is that I submitted documentation on that range behavior today...

@cgaebel an excellent addition. Will do that for sure. Thanks!

@vks
Copy link

vks commented Jun 27, 2014

I think the sentences should end in a punctuation mark. (I.e. "Secret number is {}." and "Please input guess number {}.")

@nathantypanski
Copy link

The gen_range behavior that @bachm is talking about can be shown in the code:

use std::io;
use std::rand::Rng;

static LOW: int = 1;
static HIGH: int = 101;

fn main() {
    println!("Guess the number!");

    let secret_number = std::rand::task_rng().gen_range(LOW, HIGH);
    println!("Secret number is {}.", secret_number);

    let mut guesses: int = 0;
    let mut reader = io::stdin();

    println!("The number is between {} and {}.", LOW, HIGH - 1);

    loop {
        // ...
    }

    println!("You took {} guesses!", guesses);
}

Extra concepts demonstrated:

  • Statics (is this a good thing? Traditionally, when I write ranges like this that are likely to change, I'll make them static if it's a simple program.)
  • Variable numbers of arguments to the println!() macro (wtf is the bang ! for? Using multiple args might make it clear it's a macro.).
  • Variable type declarations.
  • The code itself shows how range inclusion works.

Downsides:

  • There's no 1i anymore, and even though it could be included, it would be redundant.
  • Showing more stuff now adds conceptual bulk. We need to shoot for a happy medium.

@FreeFull
Copy link

Since this is an introductory tutorial program, wouldn't it be a good idea to have comments throughout it, explaining various finer points about how Rust handles things (such as, ranges not including the last element, or the existence of Result/Option APIs)

Edit: Looking at the concepts, comments is one of them, although the code itself doesn't have any yet.

@caipre
Copy link

caipre commented Jul 5, 2014

I think it's probably worthwhile to define a function other than main to demonstrate return as you said. You might want to avoid discussion of ownership early on though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment