Rust is a new systems programming language.
Some of Rust's features and characteristics are:
- compiled language, offering high-performance, low memory usage and requires a minimal runtime
- automatic memory management without garbage collection, based on its novel lifetime model
- statically typed, with powerful type inferrence so you almost never have to explicitly specify a variable's type
- provides algebraic data types
For more information, I recommend going through The Rust Programming Language book, available for free on the Rust documentation site.
Creating a new executable with Cargo will produce a template with the classic hello world program.
fn main() {
println!("Hello world!");
}
Let's break this (very simple) example up into parts, for clarity:
-
fn
is a keyword used to define a new function. Since functions are used a lot in procedural programs, it makes sense to keep the syntax for declaring them as short as possible. -
main
is the name of the function. The main function is special, it will be called by the operating system when the user runs the program. -
()
indicate the list of parameters received by the function. Formain
, this must always be empty. -
The
{ ... }
syntax should be familiar to anyone with previous experience in C or a C-like programming language. It indicates the start of a new block of code. -
println!
is a macro (you can tell that by the fact it's called by adding a!
to its name). Before you complain, it's important to note that Rust macros are nowhere like C macros, but rather like the Lisp ones. They are natively understood by the compiler, have proper identifier hygiene, and none of the pitfalls or limitations the C ones have.The reason
println
is a macro, not a function, is to allow the compiler to statically check that the format specifiers are correct and have corresponding arguments. You will not encounter bugs related to incorrectprintf
/scanf
format strings in Rust. -
"Hello world!"
is a string. If you're coming from a language like Python or JavaScript, be careful: double quotes ("
) are for strings and single quotes ('
) are for characters.
Declaring new variable bindings is simple, just use the
let
keyword:
let x = "Hello!";
Rust is an expression-oriented language, in the sense that you can even have blocks of code evaluate to a value, and use that recursively to form new expressions.
let x = {
let z = 2 + 2;
z
} - 5 * {
let y = 14;
y - 1
};
Notice how I didn't add semicolons (;
) to the last
expressions in each inner block. That's because Rust will give the
value of that final expression to the whole block.
You've already seen above how to define the
main
function. If you want to define other functions as
you can follow the same pattern, and perhaps take in some arguments or
return a value:
fn my_function(a: u8, b: String) -> u64 {
// ...
}
In case it's not obvious, u8
and u64
are
primitive types defining fixed-width unsigned integers.
String
is a heap-allocated array of UTF-8 characters.
Rust strings are not null-terminated; instead, they are represented as
a pointer along with the length of the allocated array.
Calling functions is also a breeze:
fn main() {
let result = my_function(123, String::from("Hello"));
// ...
}
Note the use of String::from
which creates a new owned
string from an array slice of characters, such as a string literal.
Allocations are easily visible in Rust.
You can create your own composite data types using the
struct
keyword:
struct MyStruct {
some_field: String,
some_other_field: u32,
// ...
}
Structures can be instantiated by specifying a value for each of their fields.
let my_struct = MyStruct {
some_field: String::from("hi!"),
some_other_field: 123456,
// ...
};
Traits can be used to abstract away common behavior of data types:
trait Frobnicable {
fn frobnicate();
}
impl Frobnicate for MyStruct {
fn frobnicate() { /* ... */ }
}
One great advantage of traits is that you can implement your traits on types defined externally, such as built-ins or from external libraries.