--- title: "Why Rust?" author: Lars Wirzenius ... # About me * First learnt programming in April, 1984, so 36 years ago * Been programming more or less daily ever since * BASIC, Pascal, C, a little C++, Unix shell, AWK, a lot of Python, Lisp, a little Haskell, a little Go, and now Rust * I really like Rust * Rust is like Haskell and C had a love child with unicorn fairy godmothers --- # Why I like Rust: type system * strong, static, versatile typing - helps avoid many bugs, reduces need to test every statement and branch with unit tests - not quite true that if a program compiles, it works, but Rust is a lot closer to that than C, C++, Pascal, etc - avoids entire classes of bugs: NULL pointer errors, unchecked error returns, implicit but wrong value conversions * type inference: mostly only need to specify function signatures, structs, and similar parts and the compiler deduces the rest --- # Why I like Rust: memory management * manual, a la C: malloc, free - error prone: easy to leak (no free), easy to break (more than one free), hard to debug * automatic, with garbage collection - Lisp, Python, Go - requires a runtime - despite decades of research, inevitably there is overhead and short pauses * Rust: automatic with borrow checker, lifetimes - all heap values heap are tied to values on the stack - when on-stack value is freed, the heap value is dropped - requires making sure stack values have the right lifetimes - mutability must be declared explicitly - compiler can prove lack of data races --- # Why I like Rust: misc * not controlled by a megacorporation * the ? operator and the Result type * the Option type * enums are powerful * pattern matching is powerful and exhaustive * no OOP, but traits * zero cost abstractions actually work * binaries execute quickly * excellent concurrency support --- # Why I like Rust: tooling and ecosystem * cargo, crates.io work well and avoid most pitfalls other languages have * good doctests * things get better, the community and ecosystem seem lively * avoids the 20/80 principle: problems are solved well, "pragmatic shortcuts" to allow implementation to be simpler are avoided, if the shortcut would be likely to bit users * the community is friendly and constructive --- # Not everything is perfect * things in the language and important libraries change - but almost always with backwards compatibility) * supports a lot fewer targets than C - but the big mainstream ones are supported * not well packaged for Linux distros; rustup is needed - bug as the language stabilizes, this will improve * de facto static linking only - dynamic linking works, but Rust ABI is not yet stable, so static is the default * compilation is somewhat slow --- # Example: hello ```{.rust .numberLines} fn main() { println!("Hello, world!"); } ``` --- # Example: Subplot docgen main program ```{.rust .numberLines} use chrono::{Local, TimeZone}; use pandoc; use std::fs; use std::path::Path; use std::path::PathBuf; use std::time::UNIX_EPOCH; use structopt::StructOpt; use subplot; ``` --- ```{.rust .numberLines} // Define the command line arguments. #[derive(Debug, StructOpt)] #[structopt(name = "docgen", about = "Subplot document generator.")] struct Opt { // One or more input filename. #[structopt(parse(from_os_str))] filenames: Vec, // Set output file name. #[structopt(name = "FILE", long = "--output", short = "-o", parse(from_os_str))] output: PathBuf, // Set date. #[structopt(name = "DATE", long = "--date")] date: Option, } ``` --- ```{.rust .numberLines} fn main() -> subplot::Result<()> { let opt = Opt::from_args(); let mut pandoc = pandoc::new(); // Tell Pandoc what the input files are. let first_file = &opt.filenames[0]; for filename in opt.filenames.iter() { pandoc.add_input(&filename); } ``` --- ```{.rust .numberLines} // Tell Pandoc what the input file format is. pandoc.set_input_format( pandoc::InputFormat::Markdown, vec![pandoc::MarkdownExtension::Citations], ); // Add external Pandoc filters. let citeproc = std::path::Path::new("pandoc-citeproc"); pandoc.add_option( pandoc::PandocOption::Filter( citeproc.to_path_buf())); // This would be nicer if pandoc took a Pathbuf for output, // instead of a String. Reported as // let output = format!("{}", opt.output.display()); pandoc.set_output(pandoc::OutputKind::File(output)); ``` --- ```{.rust .numberLines} // Set options for Pandoc. pandoc.add_option(pandoc::PandocOption::TableOfContents); pandoc.add_option(pandoc::PandocOption::Standalone); pandoc.add_option(pandoc::PandocOption::NumberSections); ``` --- ```{.rust .numberLines} // Metadata date from command line or file mtime. However, we // can't set it directly, since we don't want to override the date // in the actual document, if given, so we only set // user-provided-date. Our parsing code will use that if date is // not document metadata. let date = if let Some(date) = opt.date { date } else { // We have to parse the document to get the date from the // metadata. We can't set the metadata for typesetting until // we do. So we parse it twice. let doc = subplot::Document::from_file(&first_file)?; if let Some(date) = doc.meta().date() { date.to_string() } else { mtime(first_file)? } }; pandoc.add_option(pandoc::PandocOption::Meta("date".to_string(), Some(date))); ``` --- ```{.rust .numberLines} // Run Pandoc to parse the inputs into an abstract syntax tree, // then run our filter on that, then let Pandoc produce the output // file from the AST. pandoc.add_filter(|json| { let mut doc = subplot::Document::from_json(&json).expect("error parsing JSON AST"); doc.has_title().expect("document has no title"); doc.typeset(); doc.ast().expect("error serializing into JSON AST") }); pandoc.execute()?; Ok(()) } ``` --- ```{.rust .numberLines} fn mtime(filename: &Path) -> subplot::Result { let mtime = fs::metadata(filename)?.modified()?; let secs = mtime.duration_since(UNIX_EPOCH)?.as_secs(); let secs: i64 = format!("{}", secs).parse()?; let dt = Local.timestamp(secs, 0); Ok(dt.format("%Y-%m-%d %H:%M").to_string()) } ```