Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples #3562

Closed
jesse99 opened this issue Sep 23, 2012 · 21 comments
Closed

examples #3562

jesse99 opened this issue Sep 23, 2012 · 21 comments
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one.

Comments

@jesse99
Copy link
Contributor

jesse99 commented Sep 23, 2012

I'd like to see some example rust programs on the wiki or even on the main page. These should:

  • Not be purely artificial: ideally they should do something resembling useful work.
  • Be written in idiomatic, well-commented, up to date rust code.
  • Cover all the high points of the language.
  • Be executed as part of the rustc test framework.
  • Show up on the web page using syntax highlighting.
  • The web page should list names of the examples, include a brief description, and mention topics addressed by the example.
@jesse99
Copy link
Contributor Author

jesse99 commented Sep 23, 2012

I'm planning on writing four of these:

  • (ascii art) shapes: traits, structs, operator overloading
  • find-bad-words: io, tasks, rendezvous
  • json-config: multi-file exe, command line parsing, pattern matching, io, json
  • simple-lib: multi-file lib (this one probably won't do anything useful)

@jesse99
Copy link
Contributor Author

jesse99 commented Sep 23, 2012

Here's the ascii art example. I decided not to include operator overloading:

// ASCII art renderer.
// Demonstrates traits, impls, non-copyable struct, unit testing.
// To run execute: rustc --test art.rs && ./art

// Rust's core library is tightly bound to the language itself so it is automatically linked in.
// However the std library is designed to be optional (for code that must run on constrained
// environments like embedded devices or special environments like kernel code) so it must
// be explicitly linked in.
extern mod std;

// Extern mod controls linkage. Use controls the visibility of names to modules that are
// already linked in. Using WriterUtil allows us to use the write_line method.
use io::WriterUtil;

// Represents a position on a canvas.
struct Point
{
    x: int,
    y: int,
}

// Represents an offset on a canvas. (This has the same structure as a Point.
// but different semantics). In a real app we could implement traits like core::ops::Add
// to allow points and rectangles to be offset using code like: `pt + delta`.
struct Size
{
    width: int,
    height: int,
}

// Represents a rectangle on a canvas.
struct Rect
{
    top_left: Point,
    size: Size,
}

// Contains the information needed to do shape rendering via ASCII art.
struct AsciiArt
{
    // Fields are immutable, by default, so making them public won't
    // cause problems (except for making it more difficult to change
    // the names or types of fields).
    width: uint,
    height: uint,
    fill: char,
    priv lines: ~[~[mut char]],

    // This struct can be quite large so we'll disable copying: developers need
    // to either pass these structs around via borrowed pointers or move them.
    drop {}
}

// It's common to define a constructor sort of function to create struct instances.
// If there is a canonical constructor it is typically named the same as the type.
// Other constructor-ish functions are typically named from_foo, from_bar, etc. 
fn AsciiArt(width: uint, height: uint, fill: char) -> AsciiArt
{
    // Use anonymous functions to build a vector of vectors containing
    // blank characters for each position in our canvas.
    let lines = do vec::build_sized(height)
        |push|
        {
            for height.times
            {
                let mut line = ~[mut];
                vec::grow_set(line, width-1, '.', '.');
                push(line);
            }
        };

    // Rust code often returns values by omitting the trailing semi-colon
    // instead of using an explicit return statement.
    move AsciiArt {width: width, height: height, fill: fill, lines: lines}
}

// Methods particular to the AsciiArt struct.
impl AsciiArt
{
    fn add_pt(x: int, y: int)
    {
        if x >= 0 && x < self.width as int
        {
            if y >= 0 && y < self.height as int
            {
                // Note that numeric types don't implicitly convert to each other.
                let v = y as uint;
                let h = x as uint;

                // Vector subscripting will normally copy the element, but &v[i]
                // will return a reference which is what we need because the
                // element is: 1) potentially large 2) needs to be modified.
                let row = &self.lines[v];
                row[h] = self.fill;
            }
        }
    }
}

// Allows AsciiArt to be converted to a string using the libcore ToStr trait.
// Note that the %s fmt! specifier will not call this automatically.
impl AsciiArt : ToStr
{
    fn to_str() -> ~str
    {
        // Convert each line into a string.
        let lines = do self.lines.map |line| {str::from_chars(line)};

        // Concatenate the lines together using a new-line.
        str::connect(lines, "\n")
    }
}

// This is similar to an interface in other languages: it defines a protocol which
// developers can implement for arbitrary concrete types.
trait Canvas
{
    fn add_point(shape: Point);
    fn add_rect(shape: Rect);

    // Unlike interfaces traits support default implementations.
    // Which currently ICEs: see https://github.com/mozilla/rust/issues/3563
//    fn add_points(shapes: &[Point])
//    {
//        for shapes.each |pt| {self.add_point(pt)};
//    }
}

// Here we provide an implementation of the Canvas methods for AsciiArt.
// Other implementations could also be provided (e.g. for PDF or Apple's Quartz)
// and code can use them polymorphically via the Canvas trait.
impl AsciiArt : Canvas
{
    fn add_point(shape: Point)
    {
        self.add_pt(shape.x, shape.y);
    }

    fn add_rect(shape: Rect)
    {
        // Add the top and bottom lines.
        for int::range(shape.top_left.x, shape.top_left.x + shape.size.width)
        |x|
        {
            self.add_pt(x, shape.top_left.y);
            self.add_pt(x, shape.top_left.y + shape.size.height - 1);
        }

        // Add the left and right lines.
        for int::range(shape.top_left.y, shape.top_left.y + shape.size.height)
        |y|
        {
            self.add_pt(shape.top_left.x, y);
            self.add_pt(shape.top_left.x + shape.size.width - 1, y);
        }
    }
}

// Rust's unit testing framework is currently a bit under-developed so we'll use
// this little helper. The cfg is a conditional compilation attribute that enables
// check_strs only for unit testing builds.
#[cfg(test)]
pub fn check_strs(actual: &str, expected: &str) -> bool
{
    if actual != expected
    {
        io::stderr().write_line(fmt!("Found:\n%s\nbut expected\n%s", actual, expected));
        return false;
    }
    return true;
}

#[test]
fn test_ascii_art_ctor()
{
    let art = AsciiArt(3, 3, '*');
    assert check_strs(art.to_str(), "...\n...\n...");
}

#[test]
fn test_add_pt()
{
    let art = AsciiArt(3, 3, '*');
    art.add_pt(0, 0);
    art.add_pt(0, -10);
    art.add_pt(1, 2);
    assert check_strs(art.to_str(), "*..\n...\n.*.");
}

#[test]
fn test_shapes()
{
    let art = AsciiArt(4, 4, '*');
    art.add_rect(Rect {top_left: Point {x: 0, y: 0}, size: Size {width: 4, height: 4}});
    art.add_point(Point {x: 2, y: 2});
    assert check_strs(art.to_str(), "****\n*..*\n*.**\n****");
}

@jesse99
Copy link
Contributor Author

jesse99 commented Sep 23, 2012

Here's a task example:

// Searches text files for "bad" words.
// Demonstrates tasks and sharing large objects across tasks (ARC).
// To run execute: rustc --test bad_words.rs && ./bad_words
extern mod std;

use comm::{Chan, Port};
use io::WriterUtil;
use mod std::arc;
use std::arc::{ARC};

// Contains details about a file (or the unit test equivalent).
struct Contents
{
    fname: ~str,
    lines: ~[~str],
}

// Used to lazily load file contents.
type FileLoader = fn~ () -> ~Contents;

// This function runs within a dedicated task, scans through lines
// looking for words in bad, and sends a report with the results
// using chan.
fn scan_words(loader: FileLoader, bad: &~[~str], chan: Chan<~str>)
{
    let mut results = ~"";

    // Defer loading files until it's neccesary (we don't want to read in 
    // thousands of files before we even spin up a task).
    let contents = loader();
    for contents.lines.eachi
    |i, line|
    {
        for bad.each
        |b|
        {
            if line.contains(*b)
            {
                // TODO: is it efficient to concantenate strings like this?
                results += fmt!("%s:%? found %s\n", contents.fname, i+1, b.trim_right());
            }
        }
    }

    chan.send(move results);
}

// Uses multiple tasks to find the bad words in each file. Reports from tasks
// that find bad words are returned.
fn scan_files(+loaders: ~[FileLoader], +bad: ~[~str]) -> ~str
{
    fn massage_results(result: ~[@~str]) -> ~str
    {
        // We'll get the results back in a non-deterministic order so to simplify unit testing
        // we'll do a sort.
        let result = std::sort::merge_sort(|x, y| {x <= y}, result);

        // Convert to ~str so that connect works. This does do a copy, but it's not a copy
        // of a full line from a file.
        let result = do result.map |r| {copy *r};

        move str::connect(result, "")
    }

    let mut outstanding_tasks = loaders.len();
    let port = Port();
    let chan = Chan(port);

    // We take care throughout to avoid copying potentially large objects. Sending 
    // objects to tasks is especially delicate for the bad word vector because we
    // need to share it across tasks and tasks do not normally share state. However
    // we can use the std::arc module to share immutable data across tasks.
    let bad = ARC(bad);

    // Kick off a task to process each file. This can potentially kick off thousands
    // of tasks, but that's OK because tasks are much lighter-weight than threads
    // and tasks run until they finish or block (in which case another task on the
    // thread wakes up).
    //
    // We use consume here which acts like eachi except that the elements are moved 
    // into the closure instead of copied (and the vector is emptied afterwards).
    do vec::consume(loaders)
    |_i, loader|
    {
        // arc::clone is efficient because it clones the ARC, not the data the ARC contains.
        let bad = arc::clone(&bad);

        do task::spawn |move loader, move bad| {scan_words(loader, arc::get(&bad), chan)};
    }

    // Collect results until every task has finished.
    let mut result = ~[];
    while outstanding_tasks > 0
    {
        let r = port.recv();
        if r.is_not_empty()
        {
            // Push an @~str onto result so that sort will work (sort requires implicitly copyable strings).
            vec::push(result, @r);
        }
        outstanding_tasks -= 1;
    }

    move massage_results(result)
}

#[cfg(test)]
pub fn check_strs(actual: &str, expected: &str) -> bool
{
    if actual != expected
    {
        io::stderr().write_line(fmt!("Found:\n%s\nbut expected\n%s", actual, expected));
        return false;
    }
    return true;
}

// Make files large enough that the tasks actually have to do a significant amount of work.
#[cfg(test)]
fn make_good_file(fname: &str) -> FileLoader
{
    const num_files: uint = 20_000;    // underscores are ignored in numeric literals

    let fname = fname.to_unique();
    let f: FileLoader =
    ||
    {
        move ~Contents
        {
            fname: fname,
            lines: do vec::build_sized(num_files)
                |push|
                {
                    for num_files.times {push(move ~"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")}
                },
        }
    };
    f
}

// Type inference usually works very well, but closures sometimes need help which
// is why we have these two helper functions.
#[cfg(test)]
fn make_bad1(fname: &str) -> FileLoader
{
    let fname = fname.to_unique();
    let f: FileLoader = || {move ~Contents {fname: copy fname, lines: ~[~"this line is ok", ~"but ugh is not", ~"another good line"]}};
    f
}

#[cfg(test)]
fn make_bad2(fname: &str) -> FileLoader
{
    let fname = fname.to_unique();
    let f: FileLoader = || {move ~Contents {fname: copy fname, lines: ~[~"this line is ok", ~"another good line", ~"ack is not cool"]}};
    f
}

// Load num_times files (and spawn that many tasks). Up to two files
// will include bad words.
#[cfg(test)]
fn run_test(num_files: uint)
{
    let mut files = do vec::build_sized(num_files)
        |push|
        {
            for num_files.timesi
            |i|
            {
                let fname = fmt!("f%?", i);
                let loader: FileLoader =
                    match i
                    {
                        1  => make_bad1(fname),
                        42 => make_bad2(fname),
                        _  => make_good_file(fname),
                    };
                push(move loader);
            }
        };

    let bad = ~[~"ugh ", ~"ack"];
    let actual = scan_files(files, bad);
    if num_files >= 42
    {
        assert check_strs(actual, "f1:2 found ugh\nf42:3 found ack\n");
    }
    else if num_files >= 1
    {
        assert check_strs(actual, "f1:2 found ugh\n");
    }
    else
    {
        assert check_strs(actual, "");
    }
}

#[test]
fn test_empty()
{
    assert check_strs(scan_files(~[], ~[~"ack", ~"ugh"]), "");
}

#[test]
fn test_few()
{
    run_test(4);
}

#[test]
fn test_many()
{
    // On an 8-core Mac 10 system threads are created to run these 200 tasks.
    run_test(200);
}

@brson
Copy link
Contributor

brson commented Sep 23, 2012

This sounds like a great idea to me. To keep it up building we will need to put it in the repo (at least for now - I'd also like to scrape the wiki for test cases).

FWIW, I updated the FAQ recently to link to interesting bits of code around the Rust-sphere.

@jesse99
Copy link
Contributor Author

jesse99 commented Sep 23, 2012

I figured they'd go into the repo at some point. But I'd like to see some example code on the wiki for 0.4, even if there isn't time to integrate them with git and testing.

@jesse99
Copy link
Contributor Author

jesse99 commented Sep 24, 2012

Here is a multi-file crate example.

crate.rc:

// Multi file crates use an rc file to tell the compiler about various pieces of
// metadata and about which modules are part of the crate.

// This is the required bit of meta-data for a crate.
// The uuid should be generated using uuidgen (or the equivalent).
#[link(name = "json-config", vers = "1.0", uuid = "60B3CC26-037D-443A-891A-FB265232BE8D")];

// These attributes are used by rustdoc and rustc --ls.
#[author = "Jesse Jones"];
#[license = "MIT"];
#[doc = "Multi-file crate exe example"];

// These control various compiler lint passes. You can see them all by doing
// `rustc -W help`.
#[warn(unused_imports)];
#[warn(deprecated_pattern)];

// Link in the rust std library.
extern mod std;

// List our modules. When this crate is compiled the files with the same names
// will be automatically compiled into the crate.
mod main;
mod options;

main.rs:

// Uses a JSON file to store configuration options.
// Demonstrates a multi-file crate, command line parsing, pattern matching, and file IO.
// To run the executable: rustc -o json-config crate.rc && ./json-config -v

// Note that main in rust does not return an error code. If you want to return
// a non-zero exit code then use libc::exit.
fn main(args: ~[~str])
{
    let options = options::parse_command_line(args);
    io::println(fmt!("Working for %s", options.user));

    if options.verbose
    {
        io::println(fmt!("Options: %?", options));
    }
}

options.rs:

use json = std::json;
use Json = std::json::Json;
use std::map::{HashMap};
use io::WriterUtil;
use std::getopts::*;

// Various options used to control the behavior of the exe.
struct Options
{
    // these are from the config file
    user: ~str,
    poll_rate: float,

    // these are from the command line
    verbose: bool,
}

// Parses the command line, reads in a json config file, and returns the results
// using an Options struct.
#[allow(implicit_copies)]    // args is full of non-implicitly copyable
#[allow(non_implicitly_copyable_typarams)]
fn parse_command_line(args: ~[~str]) -> Options
{
    // It's good practice to do this before invoking getopts because getopts
    // will fail if a required option is missing (although at the moment all
    // of our arguments are optional).
    if args.contains(~"-h") || args.contains(~"--help")
    {
        print_usage();
        libc::exit(0);
    }

    let opts = ~[
        optopt(~"config"),        // *opt's take an optional argument
        optflag(~"h"),            // *flag's take no arguments
        optflag(~"help"),
        optflag(~"v"),
        optflag(~"verbose"),
        optflag(~"V"),
        optflag(~"version"),
    ];
    let matched = match getopts(vec::tail(args), opts)   // first argument is the exe name
    {
        result::Ok(copy m) => {m}
        result::Err(ref f) => {io::stderr().write_line(fail_str(*f)); libc::exit(1)}
    };
    if opt_present(matched, ~"version")
    {
        io::println(fmt!("json-config %s", exe_version));
        libc::exit(0);
    }
    else if matched.free.len() != 0
    {
        io::stderr().write_line(fmt!("Unexpected positional argument(s): %?", matched.free));
        libc::exit(1);
    }

    let config = if opt_present(matched, ~"config") {opt_str(matched, ~"config")} else {~"config.json"};
    let options = Options
    {
        user: ~"",
        poll_rate: 0.0,
        verbose: opt_present(matched, ~"v") || opt_present(matched, ~"verbose"),
    };
    let options = add_config(&path::Path(config), &options);
    validate_options(&options);
    move options
}

// https://github.com/mozilla/rust/issues/3567
priv const exe_version: &str = "1.0";

// Items currently default to public (package and external visibility).
// Private items are private to their module (these semantics may change in the future).
priv fn validate_options(config: &Options)
{
    if config.user.is_empty()
    {
        io::stderr().write_line("user isn't set");
        libc::exit(1);
    }
    if config.poll_rate <= 0.0
    {
        io::stderr().write_line(fmt!("poll_rate should be positive but is %f", config.poll_rate));
        libc::exit(1);
    }
}

priv fn print_usage()
{
    io::println(fmt!("json-config %s - rust sample app", exe_version));
    io::println("");
    io::println("./json-config [options]");
    io::println("--config=FILE  use a custom JSON config file (defaults to config.json)");
    io::println("-h, --help     print this message and exits");
    io::println("-v, --verbose  enable extra output");
    io::println("--version      print the version number and exits");
}

// Add informatiom from the config file to config and return a new config.
priv fn add_config(path: &Path, config: &Options) -> Options
{
    // Comments are not part of the JSON spec, but they are awfully nice
    // to have in config files so we'll pre-process the json files and strip
    // them out ourselves.
    fn read_json(reader: io::Reader) -> ~str
    {
        let mut contents = ~"";

        for reader.each_line
        |line|
        {
            if !line.trim_left().starts_with("#")
            {
                contents += line;
            }
        }

        return contents;
    }

    match io::file_reader(path)
    {
        result::Ok(reader) =>
        {
            match json::from_str(read_json(reader))
            {
                result::Ok(std::json::Dict(data)) =>
                {
                    move Options
                    {
                        user: get_config_str(path, data, ~"user"),
                        poll_rate: get_config_float(path, data, ~"poll_rate"),
                        .. *config   // this is the functional update syntax for structs
                    }
                }
                result::Ok(x) =>
                {
                    io::stderr().write_line(fmt!("Error parsing '%s': expected json::Dict but found %?.", path.to_str(), x));
                    libc::exit(1)
                }
                result::Err(err) =>
                {
                    io::stderr().write_line(fmt!("Error parsing '%s' on line %?: %s.", path.to_str(), err.line, *err.msg));
                    libc::exit(1)
                }
            }
        }
        result::Err(ref err) =>
        {
            io::stderr().write_line(fmt!("Error reading '%s': %s.", path.to_str(), *err));
            libc::exit(1)
        }
    }
}

// Search the json for a String named key.
#[allow(implicit_copies)]    // json needs to stop using ~str, see https://github.com/mozilla/rust/issues/3350
#[allow(non_implicitly_copyable_typarams)]
priv fn get_config_str(path: &Path, data: HashMap<~str, Json>, key: ~str) -> ~str
{
    match data.find(key)
    {
        option::Some(json::String(value)) =>
        {
            copy *value
        }
        option::Some(x) =>
        {
            io::stderr().write_line(fmt!("In '%s' %s was expected to be a json::String but was %?.", path.to_str(), key, x));
            libc::exit(1)
        }
        option::None =>
        {
            io::stderr().write_line(fmt!("Expected to find %s in '%s'.", key, path.to_str()));
            libc::exit(1)
        }
    }
}

// Search the json for a Num named key.
#[allow(implicit_copies)]
#[allow(non_implicitly_copyable_typarams)]
priv fn get_config_float(path: &Path, data: HashMap<~str, Json>, key: ~str) -> float
{
    match data.find(key)
    {
        option::Some(json::Num(value)) =>
        {
            value
        }
        option::Some(x) =>
        {
            io::stderr().write_line(fmt!("In '%s' %s was expected to be a json::Num but was %?.", path.to_str(), key, x));
            libc::exit(1)
        }
        option::None =>
        {
            io::stderr().write_line(fmt!("Expected to find %s in '%s'.", key, path.to_str()));
            libc::exit(1)
        }
    }
}

config.json:

{
    "user": "Joe Bob",
    # poll_rate is in seconds
    "poll_rate": 100
}

@brson
Copy link
Contributor

brson commented Sep 26, 2012

Putting these in doc/example-foo.md and hooking them up to the test driver in mk/tests.mk would be a good way to keep them building. The multi-file example would need some new support in extract-tests.py. They could be linked from the 'What next?' section of the tutorial.

@brson
Copy link
Contributor

brson commented Sep 29, 2012

@jesse99
Copy link
Contributor Author

jesse99 commented Oct 7, 2012

Here is another:

// Fixed size buffer: when it is at capacity push will drop the oldest element.
// Demonstrates custom data structure, custom iteration, operator overloading, struct encapsulation.
// To run execute: rustc --test ring_buffer.rs && ./ring_buffer
extern mod std;

// This contains the data that represents our ring buffer. In general only one 
// allocation occurs: when the struct is first created and buffer is allocated.
// Copying a RingBuffer will cause a heap allocation but the compiler will
// warn us on attempts to copy it implicitly.
struct RingBuffer<T: Copy>
{
    priv mut buffer: ~[T],
    priv capacity: uint,            // number of elements the buffer is able to hold (can't guarantee that vec capacity is exactly what we set it to)
    priv mut size: uint,            // number of elements with legit values in the buffer
    priv mut next: uint,            // index at which new elements land
}

// By convention structs are created with a function named after the type
// or with functions like from_foo.
fn RingBuffer<T: Copy>(capacity: uint) -> RingBuffer<T>
{
    let ring = RingBuffer {buffer: ~[], capacity: capacity, size: 0, next: 0};
    vec::reserve(ring.buffer, capacity);
    ring
}

// This is an impl which does not implement a trait: it merely provides some
// methods for our struct.
impl<T: Copy> RingBuffer<T>
{
    pure fn len() -> uint
    {
        self.size
    }

    pure fn is_empty() -> bool
    {
        self.size == 0
    }

    pure fn is_not_empty() -> bool
    {
        self.size != 0
    }

    pure fn buffer() -> uint
    {
        self.size
    }

    fn clear()
    {
        vec::truncate(self.buffer, 0);
        self.size = 0;
        self.next = 0;
    }

    fn push(element: T)
    {
        assert self.capacity > 0;

        if self.size < self.capacity
        {
            vec::push(self.buffer, element);
            self.size += 1;
        }
        else
        {
            self.buffer[self.next] = element;
        }
        self.next = (self.next + 1) % self.capacity;
    }
}

// This is how rust handles operator overloading. Here we provide
// an implementation for ops::Index which allows users to subscript
// a RingBuffer using the [] operator.
impl<T: Copy> RingBuffer<T> : ops::Index<uint, T>
{
    // The && represents an argument mode (in this case pass by pointer).
    // These are going away in rust 0.4.
    pure fn index(&&index: uint) -> T
    {
        assert index < self.size;

        if self.size < self.capacity
        {
            self.buffer[index]
        }
        else
        {
            self.buffer[(self.next + index) % self.capacity]
        }
    }
}

// Here we provide support for basic iteration. Doing this allows use of a bunch of
// handy functions from the iter module: eachi, all, any, filter_to_vec, map_to_vec, etc.
impl<T: Copy> RingBuffer<T> : BaseIter<T>
{
    // Typically iteration functions like each are called with the aid of the for keyword
    // which provides some sugar for looping: the closure used with for returns (),
    // break aborts the loop, loop skips to the start of the loop, and return returns a
    // value from the named function the loop was defined within.
    pure fn each(callback: fn(v: &T) -> bool)
    {
        let mut i = 0;
        while i < self.size
        {
            // T may be large or something that requires a heap allocation to copy.
            // So, by convention, each methods pass a borrowed pointer into the
            // closure.
            if !callback(&self[i])
            {
                break;
            }
            i += 1;
        }
    }

    pure fn size_hint() -> option::Option<uint>
    {
        option::Some(self.size)
    }
}

// Users can always use the %? format specifier to display the full details of
// structs (and any other type). But because of the way that elements wrap
// around this can be confusing. Here we provide a to_str method that shows
// the elements in the same order as they appear to users.
//
// Note that in this case we constrain T to be both a copyable type and a type
// that implements the ToStr trait. (The later allows the e.to_str() call to compile).
impl<T: Copy ToStr> RingBuffer<T> : ToStr
{
    fn to_str() -> ~str
    {
        let elements = do iter::map_to_vec(self) |e| {e.to_str()};
        fmt!("[%s]", str::connect(elements, ", "))
    }
}

#[test]
fn test_basics()
{
    // size 0
    let buffer: RingBuffer<int> = RingBuffer(0);    // rust type inference works very well, but not in this case
    assert buffer.len() == 0;

    // size 1
    let buffer = RingBuffer(1);
    assert buffer.len() == 0;

    buffer.push(2);
    assert buffer.len() == 1;
    assert buffer[0] == 2;

    buffer.push(3);
    assert buffer.len() == 1;
    assert buffer[0] == 3;

    // size 4
    let buffer = RingBuffer(4);
    assert buffer.len() == 0;

    buffer.push(1);
    assert buffer.len() == 1;
    assert buffer[0] == 1;

    buffer.push(2);
    assert buffer.len() == 2;
    assert buffer[0] == 1;
    assert buffer[1] == 2;

    buffer.push(3);
    assert buffer.len() == 3;
    assert buffer[0] == 1;
    assert buffer[1] == 2;
    assert buffer[2] == 3;

    buffer.push(4);
    assert buffer.len() == 4;
    assert buffer[0] == 1;
    assert buffer[1] == 2;
    assert buffer[2] == 3;
    assert buffer[3] == 4;

    // At this point the elements have wrapped around.
    buffer.push(5);
    assert buffer.len() == 4;
    assert buffer.buffer[0] == 5;

    // But the public API hides this from clients (and the private fields
    // can only be used within this module).
    assert buffer[0] == 2;
    assert buffer[1] == 3;
    assert buffer[2] == 4;
    assert buffer[3] == 5;
    assert buffer.to_str() == ~"[2, 3, 4, 5]";

    // clear
    buffer.clear();
    assert buffer.len() == 0;

    buffer.push(2);
    assert buffer.len() == 1;
    assert buffer[0] == 2;

    buffer.push(3);
    assert buffer.len() == 2;
    assert buffer[0] == 2;
    assert buffer[1] == 3;
}

// Rust uses a lot of functional programming idioms. One that takes some getting
// used to for imperative programmers is an avoidance of loops (loops rely on
// mutation of a loop variable which is not functional style). Instead looping is
// typically done with functions taking closures, the most common of which are: 
// each, map, filter, and fold.
#[test]
fn test_functional()
{
    let buffer: RingBuffer<int> = RingBuffer(4);
    buffer.push(1);
    buffer.push(3);
    buffer.push(5);
    buffer.push(2);

    // each calls a closure with each element
    // it is more functional than an explicit loop, but requires side effects in order to
    // do anything useful (because the closures user's give to each don't return values)
    let mut max = 0;
    for buffer.each |element|
    {
        if *element > max {max = *element}    // dereference because each returns elements by reference
    }
    assert max == 5;

    // map uses a closure to convert elements (possibly to different types)
    let odd = do iter::map_to_vec(buffer) |element| {element & 1 == 1};
    assert odd == ~[true, true, true, false];

    // filter returns elements for which the closure returns true
    let odd = do iter::filter_to_vec(buffer) |element| {element & 1 == 1};
    assert odd == ~[1, 3, 5];

    // fold uses the closure to combine elements together (possibly into a different type)
    // either forwards (foldl) or in reverse (foldr)
    let sum = do iter::foldl(buffer, 0) |current, element| {current + element};
    assert sum == 1 + 3 + 5 + 2;
}

@jesse99
Copy link
Contributor Author

jesse99 commented Oct 30, 2012

Forgot to add this one. It's an example of how to create and use a multi-file rust library.

First the library.

sxml.rc

// Multi file crates use an rc file to tell the compiler about various pieces of
// metadata and about which modules are part of the crate.

// This is the required bit of meta-data for a crate.
// The uuid should be generated using uuidgen (or the equivalent).
#[link(name = "sxml", vers = "1.0", uuid = "333BE970-5A76-40CD-A101-3DD27CB469E5")];
#[crate_type = "lib"];

// These attributes are used by rustdoc and rustc --ls.
#[author = "Jesse Jones"];
#[license = "MIT"];
#[doc = "Multi-file crate library example"];

// These control various compiler lint passes. You can see them all by doing
// `rustc -W help`.
#[warn(unused_imports)];
#[warn(deprecated_pattern)];

// Link in the rust std library.
extern mod std;

// List our modules. When this crate is compiled the files with the same names
// will be automatically compiled into the crate.
pub mod parsing;
pub mod validation;

xsml.rs

//! Parser for (an empty) subset of the XML specification.
//! Demonstrates a multi-file library and doc comments
//! To build the library: cd sxml && rustc sxml.rc

// Modules named the same as the rc file are special in at least the following ways:
// 1) Items listed here are automatically used by other modules in the crate (e.g. Xml in this case).
// 2) This module does not need to be listed in the rc file.
// 3) Public items go into the library module, not a module nested inside the library.

use result::{Err, Ok, Result};
use std::map::{HashMap};

// We do this to export our public API to clients of our library. This allows
// clients to use our library without worrying about the details of how it
// organizes sub-modules.
pub use parsing::*;
pub use validation::*;

pub type Attributes = HashMap<@~str, @~str>;

/// Recursive enum which vaguely resembles XML.
pub enum Xml
{
    /// Text content
    Content(@~str),

    /// Element only content: element name, attributes, and child elements
    Element(@~str, Attributes, @[@Xml]),
}

// The /// doc comment applies to the next item.
// The //! doc comment applies to the thing the comment is within (e.g. a module or a function).
// Doc comments use markdown syntax, see http://daringfireball.net/projects/markdown/syntax

/// Parses and validates XML.
/// # Usage
///     match sxml::from_str(content)
///     {
///         Ok(xml) => process(xml),
///         Err(mesg) => fail mesg,
///     }
pub fn from_str(text: &str) -> Result<Xml, @~str>
{
    do parse_str(text).chain
    |xml|
    {
        match validate_xml(&xml)
        {
            @~""   => Ok(xml),
            errors => Err(errors),
        }
    }
}

parsing.rs

//! The code that handles parsing of XML strings.

/// If the text is syntactically correct then an Xml enum is returned.
/// Otherwise an error message is returned.
pub fn parse_str(_text: &str) -> Result<Xml, @~str>  // _foo suppresses the unused variable warning
{
    Err(@~"not implemented")
}

// Lots of private items would go here.

validation.rs

//! The code that checks to see if XML is valid.

/// If the XML satisifies the DTD or schema an empty string is returned.
/// Otherwise a non-empty error message is returned.
pub fn validate_xml(_xml: &Xml) -> @~str
{
    @~""
}

// Lots of private items would go here.

Then an exe that pulls in the library.

client.rc

// Exe which uses the sxml library.
// Demonstrates linking against and using a custom rust library.
// To run the executable: cd xml-client && rustc -L ../sxml --test client.rc && ./client
#[link(name = "client", vers = "0.2", uuid = "D71AEFEB-650C-46E5-91C3-36E9406AEE8E")];

#[author = "Jesse Jones"];
#[license = "MIT"];

#[forbid(unused_imports)];
#[forbid(implicit_copies)];
#[forbid(deprecated_pattern)];
#[allow(structural_records)];   // TODO: enable some of these
#[allow(deprecated_mode)];
#[allow(non_implicitly_copyable_typarams)];

// Tell rustc which libraries to link in.
extern mod std;
extern mod sxml (name = "sxml", vers = "1.0");

mod client;

client.rs

use sxml::*;

#[test]
fn test_high_level_api()
{
    // sxml returns "not implemented" for everything...
    match sxml::from_str("<hmm/>")
    {
        Ok(*) => assert false,
        Err(mesg) => assert *mesg == ~"not implemented",
    }
}

// https://github.com/mozilla/rust/issues/3505
#[test]
fn test_low_level_api()
{
    match sxml::parse_str("<hmm/>")
    {
        Ok(*) => assert false,
        Err(mesg) => assert *mesg == ~"not implemented",
    }
}

#[test]
fn test_inner_modules()
{
    info!("hmm");
    match sxml::parsing::parse_str("<hmm/>")
    {
        Ok(*) => assert false,
        Err(mesg) => assert *mesg == ~"not implemented",
    }
}

@neuralvis
Copy link

FWIW, I am building a set of examples starting from simple ones & evolving them into more complex examples. You can find them here: https://github.com/smadhueagle/rustlings. I am also creating a utils module of various helper functions here: https://github.com/smadhueagle/rustils.

Very early stages, but I am picking up the language real fast, so expect more to come. All are based on rust-v0.4

@catamorphism
Copy link
Contributor

Nominating for milestone 4, well-covered

@graydon
Copy link
Contributor

graydon commented Jun 13, 2013

I guess I concur, though I think this bug might be too vague to know when to close.

@graydon
Copy link
Contributor

graydon commented Jun 20, 2013

just a bug, removing milestone/nomination.

@emberian
Copy link
Member

emberian commented Aug 5, 2013

Visiting for triage, nothing to add.

@emberian
Copy link
Member

Nothing to add, although https://github.com/Hoverbear/rust-rosetta is starting to pick up some examples.

@brson
Copy link
Contributor

brson commented Feb 5, 2014

I'm collecting external example sites https://github.com/mozilla/rust/wiki/Doc-examples

Would love for somebody to consolidate this stuff, put it in the official repo for maintenance.

@gsingh93
Copy link
Contributor

gsingh93 commented May 4, 2014

I'm working on writing some programs from coreutils here: https://github.com/gsingh93/rust-coreutils

I'm still pretty new to rust and I'm writing these just for practice, but at some point there might be some good examples in there.

@thestinger
Copy link
Contributor

@steveklabnik
Copy link
Member

I am not sure this is exactly relevant anymore. I think it should be closed.

@alexcrichton
Copy link
Member

I agree. We have made a lot of progress since this was opened, and I think that more specific issues should be opened if there are any lingering points to address.

RalfJung pushed a commit to RalfJung/rust that referenced this issue May 4, 2024
only show the 'basic API common for this target' message when this is a missing foreign function

Follow-up to rust-lang/miri#3558
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one.
Projects
None yet
Development

No branches or pull requests

10 participants