I first came across Rust through a project named Servo. Rust was born out of an attempt to rewrite the rendering engine behind Firefox. Since then it has been voted the “most loved language” at least two years in a row (see Stack Overflow Developer Survey 2017).
The best way to learn Rust is by reading the second edition of the official book. I purchased a copy of the book and the following are notes I took while reading the book or other resources.
One of the first things you’ll notice immediately is that
println is a macro, not a function. More generally, this is
how metaprogramming is done in Rust. For example, if you would like to
write a routine that takes a variable number of arguments or has default
values you’ll need to write a macro.
Another important bit of syntax is the semicolon, which terminates an expression. The value of the final expression in the block of the body of a function is the return value.
Cargo can be used to create a project, build the code, download and
manage dependencies. This becomes increasingly important because the
standard library in Rust is relatively small. When creating a project
use --bin to specify building a application. Cargo will
initialize a git repository by default. It manages dependencies for a
project by generating a Cargo.lock file.
std::preludconst, named with all caps/underscores, always annotated
with a type, and are always immutablelet statement:: in String::new() indicates that
new is an associated function, which is like a static
methodnew() is just a convention& to indicate a reference, but
it is much safer and easier to useResult is another convention that can be found
throughout the standard library. It is an enumeration, which consists of
variantscargo doc --open will generate documentation for all of
your dependencies and open them in your browsermatch expression is made up of arms that consist of a
pattern and the code that should be run if the value matches.char type can represent a broad range of Unicode
scalar values, which doesn’t necessarily match intuition of what a
character isf64_ as a visual separatorfmt::Debug/{:?} and
fmt::Display/{} are available for anything in
the standard library. Otherwise add #[derive(Debug)] to the
line before a definition. fmt::Display needs to be manually
implementedx = y = 6, which would assign
6 to both x and y in C{} creates a new scope and is also
an expression because it may return a value. This is also true of
if statements! Note that when assigning the return value of
a if statement to a variable it must be followed by a
else statement otherwise the return value is ambiguous if
the first conditional is false.Copy trait, while nothing
that requires allocation or is some form of resource doesstr manages an immutable pointer to memory, while
String handles allocating and deallocating memory when
expanding to shrinking the size of a string. It can be thought of as
String holding a str. It is not possible to
create a str explicitly although it is the type returned by
slices and string literals. A str is often used as a
&str, which is borrowed from a String// A string literal is simply an immutable String reference
let sl = "Hello world";
// Create a String from a string literal
let s = String::from("Hello world");let mut msr = &s[..]; or
let mut msr = &"Hello world"[..];clear() the String.struct
can be used when initializing a struct instance. This is
primarily helpful in functions where the parameters match the field
names. This is known as field init shorthand.struct Person {
name: String
}
let name = String::from("John");
Person {name};struct update syntax allows creating a new instance of
a struct based on a current instance, while changing some
fields. The final line in the block includes .. followed
immediately by the name of the current instance.structs allow you to define a struct
without field names if you want two structs to have the
same members but different typesstructs don’t have any fields and behave like
the unit type, which is what is returned by expressions that don’t
return anythingstruct that takes
&mut self as a parameter, the instance also needs to be
mutable. If self is a parameter then the method takes
ownership of the object, which may be used to transform the object. A
method that does not take self as a parameter is referred
to as an associated function, which is called using the
struct name and ::.struct each variant of an enum may
store data (e.g. no data, anonymous struct,
String, tuple). Also, a enum may define
methods.enum Option<t> { Some(T), None, }, which is included
in the prelude as well as it’s variantsif let is a nice alternative to match for
more concise code, but less checkingmod.rs, which is in turn inside a directory with the same
name as the modulemod {} or as a file
module-name.rs, which can then be included using
mod module-name.pub), it can be accessed
through any of its parent modules, however if it is private then it can
only be accessed by its immediate parent module and any of the parent’s
child modulesuse
(e.g. use a::nested::module). Bring an enums
variants into scope using {}
(e.g. use AdditivePrimaries::{Red, Green};) or glob
(e.g. use AdditivePrimaries::*;). Access a parent using
relative to the root using ::a::nested::module or using
super::modulelet v: Vec<u64>), the compiler an infer it.
For more convenience use the vec! macro
(e.g. let v: = vec![1, 2, 3];)[]
(e.g. &v[2]) will panic! if an element at
that index does not exist. Alternatively, use .get(), which
returns a Option<&T>.* to get the underlying
valuepush_str() will add the reference to another String, but I
would think that it does a copy if the original String is mutable? We
already have a mutable reference and then we try to take an immutable
one?HashMaps are a bit cumbersome and not as well
supporteduse std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();static mut variables that are similar
to globals, but their use must be in a unsafe function or block