Feed for my posts about learning Rust. I don't want these syndicated to Planet Debian, so they have their own feed.

Parsing

  • Nom seems powerful and useful, but I'm going to skip this chapter for now. I'll return to it, or read the docs directly, when I need to do some parsing.

Pain points

  • "What the notation says is that the output strings live at least as long as the input string." — this seems like a bug, I think. Surely the output strings live at most as long as the input string?

  • This chapter has little new stuff, but reminders of things that Rust programmers need to take care of.

The End

I've now read through the entire GITR book, except the parsing chapter. It's time to start writing Rust code. My first real project will be to rewrite my summain tool in Rust.

Posted Mon Oct 1 09:04:00 2018 Tags:
  • I use Python in a heavily object-oriented manner, but I've learnt to mostly avoid some aspects, such as inheritance. I don't need Rust to be OO.

  • Traits seem like a better approach. It stresses the interface aspect of inheritance, which is much nicer than the mess that often results from the implementation sharing aspect.

  • First example of Rust macros are implemented. This seem quite powerful, and may well be a legshooting device. I shan't investigate making macros of my own until I'm comfortable with the language otherwise.

Posted Sun Sep 30 08:30:00 2018 Tags:
  • I'm excited about this. Mainstream CPUs have been gaining more cores or hyperthreads since the early 2000s, but none of my programming languages have been particularly good at making use of those. C is just too complicated, and Python has the global interpreter lock, which ruins concurrency a lot. Python's getting better, but not well enough. Concurrency was one of the reasons I wanted to learn Haskell in 2003, but being a bear with a very small brain, I still haven't learnt much Haskell.

    Rust promises to make use of threads be much safer than using them in C, and that would be a really good thing.

  • This chapter also introduces networking, which also is exciting. Much of what I've done in recent years has been web API implementations, and I am looking forward to doing that in Rust.

  • New thing: std::cell::Cell with methods new, get, and put.

  • New thing: std::cell::RefCell with methods new, borrow, and borrow_mut. Borrow rules apply, but are checked runtime. Mutable borrows should be used sparingly.

  • New thing: std::rc::Rc — essentially a reference counted Box. Each Rc (clone of the original one) has an immutable reference to a heap value, and together they manage the reference count. When the count goes to zero, the heap value is freed. This is a bit like manual memory management, but piggy-backing Rust's normal memory management. Allows safe-ish data sharing for when the normal Rust rules get too much in the way. Involves some runtime overhead.

  • New thing: std::thread, especially spawn. A thread executes a closure. Threads are objects like others, e.g., they can be kept in vector to be joined.

  • Important point: threads need to move values, not borrow. If they borrow, the reference to the value they have may outlive the value in the original thread, which would be a bug. Values with 'static lifetimes can be borrowed across threads. Rc is not thread safe and can't be used to share references across threads. std::sync::Arc is a thread-safe version, with more runtime overhead.

    This seems fundamental for making threaded code safe: tightly controlled sharing of data and compile-time checks for violations of such sharing.

  • New thing: channels for inter-thread communication. A multiple-producer, single-consumer solution. There's a variant for synchronous channels, where senders block until their message has been received.

  • New thing: barriers for inter-thread synchronisation: all threads wait for all threads to reach the barriers, and then all threads continue. Presumably the barrier keeps track of how many threads have references to it, and how many are currently waiting for the barrier. This seems like a nice, easy synchronisation approach, which may still take some time to get used to.

  • New thing: mutexes. For protecting a shared resource from concurrent use. Note that this is different from barriers as it's about allowing only one thread to access the resource at a time. While the Rust mutex abstraction seems easier to use correctly than the corresponding C stuff, it's still not super-easy to be safe.

    Rust seems to provide, in the stdlib or as third-party crates, higher level abstractions that make concurrency easier to use safely. This is very good.

  • I don't understand from the GITR description what to_socket_addrs does for numeric addresses. Creates a socket that connects to the remote server+port? If not, how does the example check for addresses that are reachable hosts? What does :0 mean for port addresses?

  • Overall, Rust seems to model networking based on traditional networking concepts. Seems straightforward enough.

Posted Sat Sep 29 10:14:00 2018 Tags:
  • Error handling is one of the aspects of programming that tends to be most tedious and most error prone.

  • The ? operator in Rust makes error handling be quite easy, but still requires declaring functions as returning a Result. That's not too difficult. However, it's not enough to use use the handy operator for good error handling: at some point, something needs to actually handle the error result, or the program will crash. This is similar to exceptions in Python. But at least Rust makes it explicit what functions can return an error, and strongly guides you to handle those results.

  • New thing: std::error::Error for defining one's own errors.

  • Thought: how does Rust handle out-of-memory errors? None of the new functions return any kind of error, it seems to me, and new "objects" get created all the time. Just crash?

  • I don't really understand error-chain.

  • chain_err seems like an interesting approach.

  • Overall, looks like Rust may have some useful machinery for handling errors. More importantly, the type systems makes if more difficult to just ignore errors, which I like. I may have to use Rust for a while to appreciated the error handling, though.

Posted Wed Sep 26 14:37:00 2018 Tags:
  • Python is a "batteris included" language: the standard library that comes with a standard Python installation is quite large. Rust's is not as large, but that's OK: Cargo makes it easy to use third-party libraries (or so they say). Still, the Rust stdlib is large enough that an overview is good.

  • The API of an abstract type, such as a Vec, is not as simple as for, say, C or Python. That's because Rust has constraints on types for some methods. For example, a method for sorting is only defined for vectors of types that can be ordered. This makes the stdlib much more powerful, and allows it to include a lot more functionality than would be obvious from first glance, but does seem to me to make it harder to navigate the docs, and find things.

  • So far it seems like Rust's stdlib is good, but concentrates on giving building blocks for doing higher level things, rather than implementing such higher level things itself. The stdlib aims to be (and remaind) stable, not provide everything. This is probably good. A firm, stable base on which to build things.

  • Slices and vectors are very closely related, but this is not because there's hardcoded magic in the language, but rather the language provides tools for implementing this. Contrast with C's pointers vs arrays, which is deeply hardcoded into the language.

  • Methods one might expect for a container may be in an iterators instead. Interesting design choice. I should study the iterator methods.

  • There seems to be a lot of traits, and it will take some time to get familiar with them all. A bit of a labyrinth.

  • I don't understand where HashSet in the "Here's a shortcut, just as we defined for vectors" example comes from. Indeed, clicking on the play button shows errors. Maybe it's meant to expand on previous examples.

  • "If both the struct and the trait came from the same crate (particularly, the stdlib) then such implemention would not be allowed." I wonder if that should say "from different crates"?

Posted Sat Sep 22 15:30:00 2018 Tags:
  • New thing: modules. Mostly separate from source code files: a file can contain any number of modules.

  • Names in a module, or struct, are private by default, and must be declared with pub to be public. I like. It's much better than C (public by default) or Python (only a convention that a leading underscore makes a name private; the language doesn't enforce this).

    This kind of thing is the more helpful to larger a code base becomes, and the more code is shared between developers.

  • Also, a struct itself can be public, while it's members are not. Another good idea. Within a module struct members are public. This seems convenient, but it strikes me that keeping modules small is going to be a good idea.

  • A file src/foo.rs is a module, mod foo; is needed to use it.

  • I like that rustc (and cargo) handles building of modules automatically, and that not Makefiles are needed.

  • Summary of how modules and separate source files work:

    • Main function is in main.rs. It contains pub mod foo { pub fn answer() ... }, to define a module in the same file.

    • File bar.rs contains pub fn answer().... Can be used from main.rs with mod bar; bar::answer()

    • File yo/mod.rs contains pub fn answer().... Can be used from main.rs with mod yo; yo::answer(). Note that mod.rs is the required filename.

    • File yo/thing.rs contains pub fn answer()... and yo/mod.rs contains pub mod thing;. Can be used from main.rs with mod yo; yo::thing::answer().

  • rustc can handle all of this. It builds everything from scratch by default, which is fine for me for now. Rustc can build libraries (called crates) as well, reducing the need to build everything every time.

    Use extern foo; to use a separately built library.

  • Static linking is the default, at least for now. Makes for easier development of Rust, since there's no need for a stable ABI, and no need to rebuild everything after each Rust stable release. However, I expect Debian to favour dynamic linking, but I haven't checked.

  • I can live with static linking, but it's awkward for security updates.

  • The author says Go has a philosophical problem with dynamic linking. I wonder what that is.

  • Cargo is the Rust workflow tool and package manager. https://crates.io/ is the site for sharing Rust libraries. I'll want to see what security guarantees they provide.

  • New thing: raw string literals: r#"..."# (allows embeded newlines, and backslashes lose meaning).

  • Will need to think about how Cargo deals with versioned depenedcies.

  • I'll need to experiment with the regex and crono crates at some point. No hurry, though. I'll wait until I need them. I will probably want to play with the serde crates soon, though. They seem much likely to be useful soon in ick.

Posted Tue Sep 18 11:50:00 2018 Tags:

Today I'm not reading another chapter. Instead I want to try my hand at writing some Rust. I want to have an iterator that scans a directory tree, and returns a std::fs::DirEntry for each file, directory, or other filesystem object. I want to call the iterator something like this:

let tree = DirTree::new("/");
for entry in tree {
    println!("{:?}", entry.path.display());
}

http://git.liw.fi/rust/fswalk/ took me too long, but I got it working. Not pretty, and hides a source of errors, but good enough for today.

Posted Sat Sep 15 18:03:00 2018 Tags:
  • There's a lot of traits just for reading from files, or really anything that implement those traits. This is a bit of maze, and it'll take me a while to learn to navigate this.

  • Do iterators need to be of a particular type or implement a particular trait, or is it enoug to just have a next method that returns an Option?

  • I like that I/O errors must be handled explicitly, and that ? makes that easy. I've tended to become a little complacent with Python's exception handling.

  • I like that filenames have their own type, even if Unix would do with byte strings. So refreshing after Python.

  • We're getting to interesting bits now: doing things with filesystems and processes.

List files program

use std::env;
use std::io;
use std::path::Path;

fn main() -> io::Result<()> {
    for filename in env::args().skip(1) {
        let path = Path::new(&filename);
        find_files(&path)?;
    }
    Ok(())
}

fn find_files(root: &Path) -> io::Result<()> {
    for entry in root.read_dir()? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() {
            find_files(&path)?;
        } else {
            println!("{:?}", entry.path().display());
        }
    }
    Ok(())
}
Posted Fri Sep 14 10:33:00 2018 Tags:

Re-reading Gentle introduction to Rust, chapter 2, "Structs, Enums and Matching".

  • Important concept: moving values. Rust assignment does not, by default, copy a value, or add a reference to the value, but moves it. This means that if a variable is assigned to another, the original variable can no no longer be used. This can be controlled by doing explicit copies (clone method), or implicit ones using the copy trait.

  • This only affects "non-primitive types", basically anything that is not a machine word or a small on-stack struct. It affects especially (only?) things that are heap allocated.

  • It's all about managing memory management and borrowing.

  • I like that Rust makes this explicit and non-magic.

  • The rustdoc generated stuff have too many invisible links (titles, etc), making it difficult to click on an area to get focus there. This causes accidental navigation, and it's unnecessarily difficult to get back, for some reason. (Also, WTF do I need to move keyboard focus that way? Stupid web stuff.)

  • Important concept: variable scoping. Block scoping. Loop scope. Scope ties into memory management: when execution leaves a scope, all variables in that scope are "dropped", which can trigger heap memory to be reclaimed. This is lovely.

  • New thing: tuples. Not very exciting, but indexing syntax is a little unusual: t.42. Not sure if index can be any integral expression or if it has to be constant.

  • I dislike the example that uses first and last name fields, even as an example. It perpetrates the falsehood that everyone has first and last names.

  • New thing: structs. Not exciting, as such, but very important. Notably, these aren't classes, and Rust isn't an object oriented language. I think I'm going to like that, even if it means rearraning my brain a bit.

  • New thing: Associated functions and impl blocks. Very interesting. This feels like it'll be crucial for making clean code. Having to use them even for such common things as constructors could be a little weird, but since a constructor is going rarely going to be the only associated function, using the same approach for everything makes a lot of sense. I like that there is no magic name for the constructor, that new is merely a convention.

  • The magic &self argument to associated functions is a litle magic, but it saves having to write out the full type, so it's OK.

  • New thing: #[derive(Debug)] to automatically add the Debug trait. I expect this will become part of the boilerplate for most structs, but it's useful to have it not be mandatory, to save on code size.

  • Important thing: lifetimes. For Rust to manage heap values correctly, it needs to know how long each value needs to live. This is handled by allowing the programmer to specify the lifetime. Enables better correctness analysis by compiler, leading to fewer programming errors.

  • Important thing: traits. These provide the kind of functionality in Rust that inheritance provides in OO languages. A bit like interfaces. A trait defines an interface for a type, meaning functions that can operate on values of that type. The functions can then be implemented for different types, and Rust keeps track of which implementation is called, by virtue of static typing.

  • Also, interestingly, one can add new methods to existing types by defining new traits and implementing them. Including built-in types like integers.

  • I should eventually study the Rust std basic traits.

  • Traits are correctly used by Rust a lot. For example, to implement an iterator, you implement the std::iter::Iterator trait.

  • New thing: associated type for traits. Type parameters.

  • New thing: trait bounds. Essentially requirements on type parameters. More things to tell the compiler, so it can save me from making mistakes. Like. At the same time I foresee that this will require me to learn a lot of details.

  • New thing: enums. Much nicer than C enums.

  • I'm not sure I understand the last two examples in Enums in their full glory. Why is it OK to return the extracted string value in an Option?

  • Closures and borrowing seems complicated. I may want to stick to very simple closures for now.

  • Interesting point: Rust speed requires programmer to type more, to be more explicit about types and lifetimes and so on. Javascript, Python, etc, are terser languages, but suffer runtime speed penalties for that. I am OK with Rust's tradeoff.

  • New thing: Box.

  • New thing: type to create type aliases, like typedef in C.

Edited to add, based on feedback from my friend:

  • t.N syntax for indexing tuples only works for constants. t[i] works for any expression.

  • It seems I misunderstood associated functions. It seems an associated function is just a function in the impl block, but not a method. A method needs to also get the value (or a reference to it) as its first argument: a self, or &self, or &mut self argument. A method is an associated function, but an associated function need not be method.

  • Traits can provide default implementations for functions. This is super-powerful.

  • Re the enum full glory example: Given a variable x, when a function is called as foo(x) the value of x is moved into the function, and cannot be used on the caller. If the call is foo(&x), then the value is borrowed and so the caller can still use x. In the book's match example, what doesn't work is matching against the value, since that moves the value out of the Value, and that fails, because the Value is itself borrowed from the caller: moving anything out from a borrowed value breaks Rust's rules for keeping track of what's owned by whom. Also: you're not allowed to give away something you've borrowed, in real life, either. Having the match return a reference instead means borrowing further, and that's OK.

Posted Thu Sep 13 10:02:00 2018 Tags:

Re-reading Gentle introduction to Rust (GITR for short), the introduction and Chapter 1, "Basics". I'll be taking notes, to help me remember things. I'll note things that seem important or interesting, or new things I'm learning, or thoughts provoked by the reading material.

Introduction

  • GITR doesn't aim to reach all aspects of Rust, but to "get enough feeling for the power of the language to want to go deeper". Also, to make it easier to understand other documention, especially the Rust Programming Language book.

  • GITR doesn't seem to be dumbed down, but it does skip some of the details. That's fine.

  • Points at the Rust Users Forum, and the Rust subreddit. I've already subscribed to the subreddit RSS feed. I don't think I want to follow a web discussion board.

  • Also points at the FAQ, which I shall browser later.

  • While I don't disagree with the sentiment, GITR disses C a little more than I'd like.

  • Unifying principles of Rust:

    • strictly enforcing safe borrowing of data
    • functions, methods and closures to operate on data
    • tuples, structs and enums to aggregate data
    • pattern matching to select and destructure data
    • traits to define behaviour on data
  • GITR recommends installing Rust via the rustup script. I do wish Rust would get past that being the default.

  • I've already previously configured my Emacs to use rust-mode for .rs files. Seems to be OK.

  • Not entirly sure using rustc directly is a good idea in the long run, cargo seems like a better user experience, but it can't hurt to be familiar with both.

  • Refers to Zed Shaw. Ugh, even if the actual advice is OK in this instance.

Chapter 1: Basics

  • I don't think copy-pasting examples is a good habit, so I'll be typing in any code myself.

  • Note to self: println! adds the newline, don't include it in the string.

  • Compile-time errors for misspelt variable names? What sort of wonderful magic is this? After a couple of decades of Python, this is what I want.

  • For loops and ifs have fewer parens: nice.

  • New thing (I'd forgotten): ranges.

  • New thing: nearly everything is an expression and has a value, including ifs. Nice.

  • New thing: variables are immutable by default. This is another thing I like that Python lacks. It'll make it easier to manage large programs, where unintended mutation is a frequent source of errors for me. I expect it'll be a little annoying at first, but that I'll get used to it. I also expect it to save me at least 12765 hours of debugging in the next year.

  • New thing: type inference is also nice, but coming from dynamically-typed Python it feels like less of a win than it would if I came to Rust straight from C. However, I know from the little Haskell I learnt that type inference is crucial for a statically and strongly typed langauage to be comfortable

  • New thing: traits. These will be covered properly later, but I already know enough (from a previous partial read of GITR) that they're powerful and interesting. A bit like method overloading and subclassing, but not as messy. Traits also make things like operator overloading generic, and not as built-in as Python's __foo__ methods.

  • I like that Rust avoids implicit data type conversions, even from "safe" cases like integer to float.

  • Not sure the implicit return for functions, where the last executed expression in a function is the value returned for the function is something I'm going to like. We'll see.

  • Interesting: you can pass a reference to a constant?

  • I haven't got Rust docs installed, but I'll be using the online versions of those: https://doc.rust-lang.org/.

  • Arrays have a fixed size. Elements may be muted, but the array size is fixed at creation time. Array size is part of its type: arrays of different size have different types. Bounds checking for arrays can happen at least partially at compile-time.

  • New thing: [u32; 4] is the type of an array of four thirty-two-bit integers.

  • Slices are views into an array (or vector?). They're easier to pass as function arguments. Slices know their length, but since they're not fixed in size, different calls to a function may get slices of different sizes. A slice can be a view to only part of an array. Bounds-checking for slices is (at least primarily) at run-time.

  • New thing: &foo in Rust means "borrow foo".

  • New thing: slice.get(i) method to access a slice's element without panicing. Returns a value of type Option, which means it can return a reference to value (Some(&value)) or an indication of no value (None). This is safer than Python's None as the caller must always deal with both cases, in pattern matching. Also, safer than throwing and catching exceptions. However, calling option.unwrap() can still panic, so that's a potential trap. The unwrap_or method avoids that, though. The expect method allows giving a custom error message.

  • Vectors are re-sizeable arrays. They seem to "become slices" when passed as an argument to a function that expects a slice. The borrow operator does the type coercion. Vectors have handy methods: pop, extend, sort, dedup. The docs have more: https://doc.rust-lang.org/std/vec/struct.Vec.html.

  • Memory management in Rust is one of its stronger attractions for me.

  • New thing: iterators. A powerful concept I already know from Python. I have the impression they're even more powerful and used even more than in Python. It's interesting that GITR makes the point that iterators are more efficient than looping, due to fewer bounds checks. I like that Rust values efficiency.

  • Strings are complicates. No surprise, since they are by their very nature, because humans. Rust strings are UTF-8 strings. There's byte strings separately.

  • format! is a handy macro.

  • Having access to command line arguments and file I/O this early in the book is good.

  • New thing: closures. Similar to lambdas in Python. Powerful.

  • New thing: match. I liked pattern matching in Haskell. This is similar, but different. I like that you must handle all cases.

  • New thing: if let.

  • New thing: inclusive ranges, with three dots.

  • New thing: Result type. Similar to Option, but better suited for errors, where an error is not just None. The "built-in" Result wraps two values, one for Ok, the other Err. This gets a bit repetitive, so std::io::Result<T> is a type alias for Result<T, std::io:Error>. Shorter, easier to type.

  • New think: the ? operator to handle Result checking in a terser fashion that doesn't obscure the happy path. Only useable in functions that return a Result value.

Posted Wed Sep 12 10:36:00 2018 Tags: