Soliciting multiple new examples for rust-lang.org homepage

Hi there, Rustographers.

The Rust webpage needs one or more better examples, and we need your help to make them.

From the issue tracker the current idea is that we might replace the existing example that tries to stuff as many concepts into as few lines as possible, with several simple examples.

So, per @alilleybrinker's comment, let's come up with simple examples along the following lines:

  • Zero cost abstractions: I like the idea of showing off iterators, as they are one of the most common abstractions in Rust code, and they nicely embody this particular design goal.
  • Move semantics: This one is difficult, because there is a question of how much we want to include. We can go all out and make it about how everything is moved by default, unless it implements Copy, or as simple as showcasing what a move is in Rust. I am not sure here. If it's something about Copy it could be along the lines of defining two structs, both containing an i32, one of which implements Copy while the other doesn't, along with an example assignment for both of them.
  • Guaranteed memory safety: The first thing to look at here is exactly what is meant by memory safety. Per the Rust language reference, Rust promises that no code will do the following, which is guaranteed by the compiler for safe code, and assumed to be checked by the programmer for unsafe code. Any example should likely showcase the inability to do any of these three things.
    • Dereference a null/dangling raw pointer
    • Read undef (uninitialized) memory
    • Break the pointer aliasing rules with raw pointers (a subset of the rules used by C)
  • Threads without data races: There is a lot of good stuff to work with here, with a lot already written about Rust's excellent systems for concurrency. We can likely build off prior work quite easily.
  • Trait-based generics: Once again, it should be pretty easy. Likely best to take a trait that already exists, define a struct and implement that trait for that struct, and then use a primitive type and the struct in a function provided by that trait. Add would be a pretty straightforward one.
  • Pattern matching: I like your idea, and can't think of anything better.
  • Type inference: Lots of good options here. We likely want to make it clear that types can be inferred even in a lot of complicated places, and that types are only mandatory in function signatures (or where there is an ambiguity that can't be resolved without an annotation).
  • Minimal runtime: I am not sure this can be shown in a static code example. This may be something best left to the book.
  • Efficient C Bindings: C bindings are important. Rust's ability to very easily interoperate with C code is a major selling point. We can likely adapt something from the book here.

I'll start:

Zero-cost abstractions:

fn main() {
    // Closures have stack-allocated environments
    // and are statically-dispatched.
    let offset = 10;
    println!("{}", [1, 2, 3].map(|x| x + offset));
}
1 Like

is part of the idea to have examples that fail to compile?

In other words, it is hard to demonstrate that certain constructs are illegal without showing a compilation failure . . . but a program that fails to compile can be a weird thing to put on the home page.

Yes, I'd be a big fan of showing thinhs that don't compile, because to me that's actually the important thing. You can show off tons of stuff and maybe it'll be a bit cleaner than C++, but ultimately people will say "well you can do that in xyz". The fact that it can't be used in various incorrect ways is the important thing.

But maybe that makes for a terrible landing example. My ideal version of this basically has the correct code and several busted versions of the code that you can tab through. I think that'd be pretty slick, but idk.

Personally I'd demo scoped threads, but you need an external crate for that. Anything "super cool" really needs external crates. Would it be viable to finally add some blessed crates to playpen? Possibly using cargoscript for specifying deps?

1 Like

If we're looking to do an example for each of the items in the above list (which probably isn't advisable, although the list at least provides a jumping off point), then the following can likely be done without negative (doesn't compile) examples:

  • Zero-cost abstractions
  • Move semantics
  • Trait-based generics
  • Pattern matching
  • Efficient C bindings

I've been thinking as well about how to present these, and I actually like the clarity and simplicity of the Elixir site's design for this (http://elixir-lang.org/). Collectively, the small paragraphs and code examples form a nice little introduction to the language.

I've been thinking about another thing spurred by a comment of @steveklabnik's on Hacker News a while back. What if Rust had three small introductions to the language, each targeted to one of the three groups Steve identifies in this comment (functional programmers, systems programmers, and scripting programmers)? This is probably tangential to the homepage issue here, but it may be interesting to make these three small introductions and then direct users to them at the bottom of the homepage's introduction. So newcomers get a general introduction, and then one more tailored to both showcasing the features they are likely to appreciate, and introducing those they may struggle with using language familiar to them.

Move semantics

fn main() {
    use std::thread;
    let mut foo = vec![];

    let child = thread::spawn(move || {
        foo.push(1);
        foo.push(2);
        foo
    });

    // foo cannot be accessed here, because the thread
    // might still write to it. Usage of foo at this point will be
    // prevented at compile-time.

    foo = child.join().unwrap();
    
    // foo has now been moved back from the thread, and we
    // can access it again to print its content.

    println!("{:?}", foo);
}

3 Likes

It would be really nice if we could show off not just the language, but also the ecosystem - there are crates out there which demonstrate the functionality you list really well, and it would be great to show off how easy it is to use cargo to pull in extra functionality.

Of course, that would need extra work, since the playpen would need to be able to use cargo and packages outside of the standard library - you'd be able to show off more real world/potentially useful code though.

It would also give you free examples - basically all the useful/popular crates have plenty of useful samples which could be dropped straight in.

Zero cost abstractions:

I think this is better expressed in text and maybe with a few short examples. Maybe
explain what exactly this means (from example in RFC):

'
// Closures have stack-allocated environments
// and are statically-dispatched.
'

Not every developer will be coming form a language where understanding the
difference between a stack or heap allocation is critical information. Static
dispatch will be especially confusing to those developers, since the concept
essentially would not exist to someone using Python.

Move semantics:

struct Item {
    pub inner_value: u32
}

fn take_item(item: Item) {
    println!("Took: {}", item.inner_value);
}

fn borrow_item(item: &Item) {
    println!("Borrowed: {}", item.inner_value);
}

fn main() {
    let item = Item {inner_value:5};


    // 'borrow_item' borrows the item, it takes an immutable reference to it
    borrow_item(&item);
    // Prints '5'
    println!("{}", item.inner_value);

    // item is moved into function
    take_item(item);

    // Compile time error - use of moved value. X was taken by 'take_item'.
    println!("{}", item.inner_value);
}

Guaranteed memory safety:
I think a good example here would have to involve some vulnerable code alongside
some equivalent rust code.

This C code compiles with no warnings on gcc.

#include <stdlib.h>
#include <stdio.h>
int main(){
    int *ptr;
    // Allocate memory
    ptr=(int*)malloc(1*sizeof(int));
    // Release memory
    free(ptr);

    // Use After Free
    // https://www.owasp.org/index.php/Using_freed_memory
    printf("Printing a dereferenced pointer after it is free'd %d", ****ptr); // fix pointers, annoying markdown thing

    // Double-Free
    // https://www.owasp.org/index.php/Double_Free
    free(ptr);
    return 0;
}


Rust outright prevents these and much more complex mistakes at compile time

struct Item {
    inner_value: u32
}

fn main() {
  let item = Item{inner_value: 5};
  // Drop is a bit like 'free', when we drop 'item' we are stating it is no longer usable
  drop(item);

  // We are prevented, at compile time, from using 'item'. Rust prevents a "use after free" here.
  println!("{}", item.inner_value);
}

Threads without data races:
I'd just show the difference between a single threaded program and a non single
threaded program. It's barely any extra work to make something parallel in rust.

Trait-based generics:


trait Animal {
    fn make_noise(&self);
}

struct Cat;
struct Dog;

// Implement the trait Animal for Cat
impl Animal for Cat {
  // To satisfy the Animal trait we must implement the make_noise function
    fn make_noise(&self) {
        println!("Meow!");
    }
}

// Implement the trait Animal for Cat
impl Animal for Dog {
    fn make_noise(&self) {
        println!("Woof!");
    }
}

// Takes a reference to anything that implements the Animal trait
fn noisy_fn(animal: &Animal) {

    // Call 'make_noise', which all Animal types must implement
    animal.make_noise();
}

fn main() {
  // Create our Dog
  let dog = Dog;
  // create our cat
  let cat = Cat;

  // prints "Woof!"
  noisy_fn(&dog);
  // prints "Meow!"
  noisy_fn(&cat);
}

Pattern matching:

I actually think the fizz buzz that uses pattern matching is a really fun example.

Enums(my addiction):

I will also add that after pattern matching would be a good time to show
enums.

Use an example everyone ends up running into,
the 'Maybe' or Option type.

// An Optional<T> can either be a Nothing or a 'Something containing a T'
pub enum Optional<T> {
    Nothing,
    Something(T),
}

fn main() {
    let nothing : Optional<&str> =  Optional::Nothing;
    let something = Optional::Something("hello");

    let vec = vec![nothing, something];

    for thing in vec {
      // You can pattern match against enums.
        match thing {
            Optional::Nothing   => println!("Nothing"),
            Optional::Something(value)  => println!("Something: {}", value)
        }
    }

}

The last 3 are ok but idk of good ways to show them.

People who are going to be looking at these will be new people to the language. So the examples should be as small as possible, and only introduce a single concept at a time if possible.

I think @mrmonday is correct - showing off the environment is important. Cargo is such a huge tool if you're coming from C++, or even Python/ languages with not-amazing package management. There are thousands of crates, maybe taking a few of the 'crate of the weeks' and displaying them would be a good idea, or somehow expressing that rust is usable for a lot of projects already.

Linking to rustbyexample.com would also be a good idea. Since it's basically just page after page of examples.

3 Likes

I know the real estate is limited, but #[derive(Display)] is one of those nifty features that makes me happy every time.

Maybe an annotation that indicates that compiling the program should only succeed if compiling a given commented line fails?

Here's a thought: so much of Rust's value comes from the things you can't do, rather than the things you can. As such, conventional "here's how to do $X" examples might not really communicate why Rust is so awesome.

So what about having some "comparison" examples? Start with a chunk of C/C++/other code that has some problem with it (might want to make it uneditable and hard-code the compiler and runtime output). Then, have a button or tab or something that switches over to the Rust version of the code, demonstrating that it doesn't compile and/or fails cleanly at runtime. Finally, have another tab/button that "fixes" the code to be idiomatic Rust that does work.

It's hard to do without C++ devs claiming foul play, since any code short enough to be illustrative is likely not idiomatic modern c++.

On the flip side, any sufficiently complicated code will be labelled as "oh, no one who knows what they're doing will ever do that". You can't win. :stuck_out_tongue:

How about this idea:

Have two example windows, side by side. One showing examples of idiomatic code that compiles, the other showing examples of code that does not compile due to Rust safety guarantees.

What about a simple example in C++, then an idiomatic version in C++ (to show how much work is necessary), followed by a Rust version that fails to compile and then the idiomatic Rust. ... Actually that sounds like so many steps already. Maybe short and simple and broken C++and Rust side by side with a tab or button to switch to idiomatically correct versions of the two.

I don't think it is a good idea to put C++ on Rust homepage. There is no need to define Rust through competition with another language.

3 Likes

Just C++ would probably be a mistake, but a variety of languages might be interesting. Then again, I think not defining Rust in terms of other languages is a good point.

The homepage should present Rust, in terms of what Rust can do and what Rust prevents. I have nothing against detailed comparison of Rust to other languages, but it should be somewhere else.

4 Likes

I think, macros are also one of those features, which is nice of Rust.
Maybe, systems programmers are used to have it, but someone from a Ruby/Java/Python/... background has probably not. Web-people will have experience with preprocessors, but macros are a bit more than just string replacements.

I just don't have a handy example.. this one feels more like a job for impl Person Rust Playground :slight_smile:

I don't think this is particularly apropos, but what can I say, I love stack machines. :stuck_out_tongue:

// This code is editable and runnable!
fn main() {
    let program = "1 2 3 swap rot * + 4 swap - -2 /";
    let mut stack: Vec<i64> = vec![];
    macro_rules! pop { () => {stack.pop().expect("stack empty")} }
    macro_rules! push { ($($e:expr),*) => {{$(stack.push($e);)*}} }
    for word in program.split_whitespace() {
        match word {
            "+" => { let v = pop!() + pop!(); stack.push(v); }
            "-" => { let v = pop!() - pop!(); stack.push(v); }
            "*" => { let v = pop!() * pop!(); stack.push(v); }
            "/" => { let v = pop!() / pop!(); stack.push(v); }
            "swap" => { let (b,a) = (pop!(),pop!()); push!(a,b) }
            "rot" => {
                let (c,b,a) = (pop!(),pop!(),pop!());
                push!(b,c,a);
            }
            other => stack.push(other.parse().expect("not integer"))
        }
    }
    println!("result: {}", pop!());
}

Also, it's really hard to fit anything interesting into the existing space: 69 wide, 21 high.

3 Likes

How about an example showing how to use the Entry api in HashMaps. I always feel that mos of these simple examples fail to show why it is useful and what it can provide. The Entry api shows how rust's constraints can allow a very nice, safe and fast api that is not really possible in most (any?) other languages. In addition, I think it shows that the rust standard library is not bad in terms of apis. The example should, as was suggested here, have comments explaining how the Entry object means that the HashMap can't be modified, etc...