Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rust as a CLI language

Rust as a CLI language

Talk given at Linuxing in London

1c88d7906e3ffa450aedff2f5f1d1299?s=128

Florian Gilcher

January 17, 2018
Tweet

Transcript

  1. None
  2. $ cat ~/.profile GIT_AUTHOR_NAME=Florian Gilcher GIT_AUTHOR_EMAIL=florian@asquera.de TWITTER_HANDLE=argorak GITHUB_HANDLE=skade BLOG=skade.me YAKS=yakshav.es

  3. • Rust and Elasticsearch Trainer • Event organiser • Ruby

    Programmer since 2003 • Rust Programmer since 2013 • CEO asquera GmbH
  4. • Community person • Rust/Search Meetups • eurucamp/jrubyconf.eu • RustFest

    • https://isleofruby.org/
  5. Part of the global Rust community team

  6. As a hobby, I shoot arrows at stuff

  7. Rust as a CLI language

  8. What is Rust?

  9. • new systems programming language • powers and was developed

    in along with Servo, a new browser engine • by Mozilla and the Community • First stable release May 15th, 2015
  10. https://www.rust-lang.org/friends.html

  11. Providing an alternative to C/C++, but also higher-level languages.

  12. • Safe • Concurrent • Fast

  13. It’s generally perceived that safety, especially memory-safety comes at a

    runtime cost.
  14. • Safe • Concurrent • Fast

  15. Pick Three

  16. Core features

  17. • Static type system with local type inference • Explicit

    mutability • Zero-cost abstractions • Runtime-independent concurrency safety
  18. • Errors are values • No null • Static automatic

    memory manage- ment • No garbage collection
  19. A short introduction

  20. extern crate tempdir; use tempdir::*; use std::fs::File; fn main() {

    let tempdir = TempDir::new("goto-berlin"); let mut tempfile = match tempdir { Ok(dir) => { File::create( dir.path().join("tmpfile") ) } Err(_) => { panic!("Couldn’t open tempdir") } } do_something(&mut tempfile); // look, no close necessary! }
  21. Base concept: Mutability

  22. struct InnerData { val: i32 } struct Data { inner:

    InnerData } fn main() { let d = Data { inner: InnerData { val: 41 }}; d.inner.val = 42; // error: cannot assign to immutable field `d.inner.val` }
  23. struct InnerData { val: i32 } struct Data { inner:

    InnerData } fn main() { let mut d = Data { inner: InnerData { val: 41 }}; d.inner.val = 42; }
  24. Base concept: Ownership & Borrowing

  25. • Every piece of data is uniquely owned • Ownership

    can be passed • When owned data reaches the end of a scope, it is destructed
  26. use std::fs::File; use std::io::Write; fn main() { let file =

    File::open("test") .expect("Unable to open file, bailing!"); take_and_write_to_file(file); // take_and_write_to_file(file); // ^^ Illegal } fn take_and_write_to_file(mut file: File) { writeln!(file, "{}", "Hello #gotober!"); }
  27. • Access can be borrowed (mutable and immutable) • You

    can borrow mutably once • Or multiple times immutably • Exclusive: mutable or immutable, never both
  28. Shared mutable state is an issue even single-threaded applications!

  29. use std::fs::File; use std::io::Write; fn main() { let mut file

    = File::open("test") .expect("Unable to open file, bailing!"); write_to_file(&mut file); write_to_file(&mut file); } fn write_to_file(file: &mut File) { writeln!(file, "{}", "Hello #gotober!"); }
  30. fn main() { let mut vector = vec![1,2,3]; let elem

    = &vector[1]; vector[2] = 4; }
  31. error[E0502]: cannot borrow `vector` as mutable –> src/main.rs:4:5 | 3

    | let elem = &vector[1]; | —— immutable borrow occurs h 4 | vector[2] = 4; | ^^^^^^ mutable borrow occurs here 5 | } | - immutable borrow ends here
  32. Rust checks validity of all references at compile-time.

  33. struct Data<’a> { inner: &’a i32 } fn return_reference<’a>() ->

    Data<’a> { let number = 4; Data { inner: &number } }
  34. –> src/main.rs:8:20 | 8 | Data { inner: &number }

    | ^^^^^^ does not live long 9 | } | - borrowed value only lives until here |
  35. All Rust function signatures not only signal data types, but

    also mutability, ownership and interconnections between input and output types.
  36. 100, 1000, 10.000 lines of called code, Rust keeps these

    properties!
  37. Abstractions Rust provides higher-level abstractions through Generics and Traits, similar

    to C++ Templates or Java Generics.
  38. Low-level control & safety!

  39. • Borrows boil down to pointers at runtime • Values

    are plain values just like in e.g. C • Optional unsafe sub-language
  40. “ Safe code means you can take better risks.” –

    @QEDunham
  41. "I stopped writing Python or Ruby, I write my small

    tools in Rust now."
  42. rustup http://rustup.rs or your package manager

  43. rustup rustup allows full management of a Rust development toolchain,

    including cross-compilation.
  44. rustup In general, it is recommendable to target a rustc

    that is shipped with your target system.
  45. rustup $ rustup install stable

  46. Minimum version The minimum version for the following example is

    Rust 1.15.
  47. Writing a small CLI program

  48. We’re currently collecting community blogposts and I lost count. Luckily,

    a kind soul offers a JSON feed on readrust.net.
  49. { "version": "https://jsonfeed.org/version/1", "title": "#Rust2018", "home_page_url": "http://readrust.net/", "feed_url": "http://readrust.net/rust2018/feed "description":

    "Posts from the Rust 2018 initi "author": { "name": "Wesley Moore", "url": "http://www.wezm.net/" }, "items": [ {
  50. Errors enum Result<T,E> { Ok(T), Err(E) }

  51. Quick error handling in CLI let file = File::open("foo").unwrap(); //

    This will quit if the File cannot be opened
  52. Strings Rust has 2 types of strings: &str and String.

    The first is harder to use then the second. They are compatible.
  53. Quickly handling Strings At the beginning, whenever you encounter &str

    and are confused, use this: let heap_allocated_string = "normal_string".to_string();
  54. Task A proper CLI tool that gets the feed, parses

    it, prints it in a nice, readable fashion.
  55. readrust 0.1 Florian G. <florian.gilcher@asquera.de> Reads readrust.net USAGE: readrust [FLAGS]

    [OPTIONS] FLAGS: -c, –count Show the count of posts -h, –help Prints help information -V, –version Prints version information OPTIONS: -n, –number <NUMBER> Only print the NUMBE
  56. We need: • Argument parser • HTTP client • JSON

    parser • A pretty-printer
  57. cargo $ cargo init readrust-cli $ cargo build [–release]

  58. Feel free to clone https://github.com/skade/readrust-cli

  59. cargo manifest [package] name = "readrust" version = "0.1.0" authors

    = ["Florian Gilcher <florian.gilcher@asq [dependencies] reqwest = "0.8" clap = "2.29" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" prettytable-rs = "0.6"
  60. CLAP Command Line Argument Parser https://clap.rs

  61. • Flexible argument parser • Automatic USAGE generation • Well

    documented
  62. extern crate clap; use clap::App; fn main() { let app

    = App::new("readrust") .version("0.1") .author("Florian G. <florian.gilcher@asquera.de>") .about("Reads readrust.net") .args_from_usage("-n, –number=[NUMBER] ’Only print the NUMBER most recent posts’ -c, –count ’Show the count of posts’"); let matches = app.get_matches(); }
  63. $ cargo run – –help readrust 0.1 Florian G. <florian.gilcher@asquera.de>

    Reads readrust.net USAGE: readrust [FLAGS] [OPTIONS] FLAGS: -c, –count Show the count of posts -h, –help Prints help information -V, –version Prints version information OPTIONS: -n, –number <NUMBER> Only print the NUMBER most recent posts
  64. Well, that was quick.

  65. HTTP reqwest, the high-level http client

  66. • Easy to use • synchronous and asynchrous modes •

    Documented
  67. fn get_feed() -> String { //? }

  68. extern crate reqwest; pub static URL: &’static str = "http://readrust.net/rust2018/fee

    fn get_feed() -> String { let client = reqwest::Client::new(); let request = client.get(URL); let mut resp = request.send().unwrap(); assert!(resp.status().is_success()); resp.text().unwrap() }
  69. JSON parsing: SERDE

  70. SERDE: Serialization, Deserialization

  71. • Generic framework • Generic and strict parse modes •

    JSON parser one of the fastest around • good documentation
  72. fn get_feed -> Feed { //? }

  73. #[derive(Debug, Deserialize, Serialize)] pub struct Author { name: String, url:

    String, }
  74. #[derive(Debug, Deserialize, Serialize)] pub struct Item { id: String, title:

    String, content_text: String, url: String, date_published: String, author: Author, }
  75. #[derive(Debug, Deserialize, Serialize)] struct Feed { version: String, title: String,

    home_page_url: String, feed_url: String, description: String, author: Author, items: Vec<Item>, }
  76. fn get_feed() -> Feed { let client = reqwest::Client::new(); let

    mut request = client.get(URL); let mut resp = request.send().unwrap(); assert!(resp.status().is_success()); let data = resp.text().unwrap(); serde_json::from_str::<Feed>(&data).unwrap() }
  77. Simply printing fn print_count(feed: &Feed) { println!("Number of posts: {}",

    feed.items.len()); }
  78. Using that let feed = get_feed(); if matches.is_present("count") { print_count(&feed);

    }
  79. Showing blog posts Let’s use prettytable-rs. It prints out data

    easily as a table.
  80. prettytable-rs • Easy to use • Supports subtables and colours

    • Documented
  81. fn fn print_feed_table<I: Iterator<Item = Item>>(items: I) { let mut

    table = prettytable::Table::new(); table.add_row(row!["Title", "Author", "Link"]); for item in items { let title = if item.title.len() >= 50 { &item.title[0..50] } else { &item.title }; table.add_row(row![title, item.author.name, item.url]); } table.printstd(); }
  82. Using it if matches.is_present("count") { print_count(&feed); } else { let

    iter = feed.items.into_iter(); if let Some(string) = matches.value_of("number") { let number = string.parse().unwrap(); print_feed_table(iter.take(number)) } else { print_feed_table(iter) } }
  83. We’re done

  84. • A 79-line program • With full build and dependency

    tooling • No typing ceremony except the Feed definition
  85. Advantages

  86. • Easily shippable as a binary • Full error handling

    (even if we just bail!) • Fully documented dependencies
  87. When and why to pick up Rust • You want

    to learn something new • You want that extra bit of perfor- mance • You want the control of C, but the safety of Python • You want to parallelise things
  88. Learning experience Rust takes around three weeks to click in

    full.
  89. Continuing • Do proper error passing and central handling •

    Add logging (using... log!) • Filter the items • Make statistics over the items
  90. Rust is a surprisingly productive language with great compiler diagnostics

    that scales from lowlevel to "just quick" programs.
  91. https://rust-lang.org https://www.meetup.com/Rust- London-User-Group/ https://www.meetup.com/Cambridge- Rust-Meetup/

  92. https://this-week-in-rust.org

  93. Have a question? community-team@rust-lang.org

  94. None
  95. Concurrency without fear

  96. let counter = Counter { count: 0 }; for _

    in 1..3 { std::thread::spawn(move || { increment(&mut counter); // capture of moved value: `counter` }); }
  97. use std::rc::Rc; let rc = Rc::new(Counter { count: 0 });

    for _ in 1..3 { let handle = rc.clone(); std::thread::spawn(move || { // `std::rc::Rc<Counter>` cannot be sent between threads safely increment(&mut handle); }); }
  98. use std::sync::{Arc,Mutex}; let rc = Arc::new(Mutex::new(Counter { count: 0 }));

    for _ in 1..3 { let handle = rc.clone(); std::thread::spawn(move || { increment(&mut handle); }); }
  99. This example could be a concurrency bug in many languages,

    or even a double-free!
  100. This analysis is purely static and independent of concurrency primitive!

    Rusts type system allows no data races.