Key takeaways
1. Hello World
This is the source code of the traditional Hello World program.
// This is a comment, and is ignored by the compiler
// You can test this code by clicking the "Run" button over there ->
// or if you prefer to use your keyboard, you can use the "Ctrl + Enter" shortcut
// This code is editable, feel free to hack it!
// You can always return to the original code by clicking the "Reset" button ->
// This is the main function
fn main() {
// Statements here are executed when the compiled binary is called
// Print text to the console
println!("Hello World!");
}
println!
is a macro that prints text to the console.
A binary can be generated using the Rust compiler: rustc
.
$ rustc hello.rs
rustc
will produce a hello
binary that can be executed.
$ ./hello
Hello World!
Activity
Click 'Run' above to see the expected output. Next, add a new line with a second println!
macro so that the output shows:
Hello World!
I'm a Rustacean!
1.1 Comments
Any program requires comments, and Rust supports a few different varieties:
Regular comments which are ignored by the compiler:
// Line comments which go to the end of the line.
/* Block comments which go to the closing delimiter. */
Doc comments which are parsed into HTML library
documentation:/// Generate library docs for the following item.
//! Generate library docs for the enclosing item.
fn main() {
// This is an example of a line comment
// There are two slashes at the beginning of the line
// And nothing written inside these will be read by the compiler
// println!("Hello, world!");
// Run it. See? Now try deleting the two slashes, and run it again.
/*
* This is another type of comment, a block comment. In general,
* line comments are the recommended comment style. But
* block comments are extremely useful for temporarily disabling
* chunks of code. /* Block comments can be /* nested, */ */
* so it takes only a few keystrokes to comment out everything
* in this main() function. /*/*/* Try it yourself! */*/*/
*/
/*
Note: The previous column of `*` was entirely for style. There's
no actual need for it.
*/
// You can manipulate expressions more easily with block comments
// than with line comments. Try deleting the comment delimiters
// to change the result:
let x = 5 + /* 90 + */ 5;
println!("Is `x` 10 or 100? x = {}", x);
}
See also:
1.2 Formatted print
Printing is handled by a series of macros
defined in std::fmt
some of which include:
format!
: write formatted text toString
print!
: same asformat!
but the text is printed to the console (io::stdout).println!
: same asprint!
but a newline is appended.eprint!
: same asformat!
but the text is printed to the standard error (io::stderr).eprintln!
: same aseprint!
but a newline is appended.
All parse text in the same fashion. As a plus, Rust checks formatting correctness at compile time.
fn main() {
// In general, the `{}` will be automatically replaced with any
// arguments. These will be stringified.
println!("{} days", 31);
// Without a suffix, 31 becomes an i32. You can change what type 31 is
// by providing a suffix. The number 31i64 for example has the type i64.
// There are various optional patterns this works with. Positional
// arguments can be used.
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
// As can named arguments.
println!("{subject} {verb} {object}",
object="the lazy dog",
subject="the quick brown fox",
verb="jumps over");
// Special formatting can be specified after a `:`.
println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
// You can right-align text with a specified width. This will output
// " 1". 5 white spaces and a "1".
println!("{number:>width$}", number=1, width=6);
// You can pad numbers with extra zeroes. This will output "000001".
println!("{number:0>width$}", number=1, width=6);
// Rust even checks to make sure the correct number of arguments are
// used.
println!("My name is {0}, {1} {0}", "Bond");
// FIXME ^ Add the missing argument: "James"
// Create a structure named `Structure` which contains an `i32`.
#[allow(dead_code)]
struct Structure(i32);
// However, custom types such as this structure require more complicated
// handling. This will not work.
println!("This struct `{}` won't print...", Structure(3));
// FIXME ^ Comment out this line.
}
std::fmt
contains many traits
which govern the display of text. The base form of two important ones are listed below:
fmt::Debug
: Uses the{:?}
marker. Format text for debugging purposes.fmt::Display
: Uses the{}
marker. Format text in a more elegant, user friendly fashion.
Here, we used fmt::Display
because the std library provides implementations for these types. To print text for custom types, more steps are required.
Implementing the fmt::Display
trait automatically implements the ToString
trait which allows us to convert the type to String
.
Activities
- Fix the two issues in the above code (see FIXME) so that it runs without error.
- Add a
println!
macro that prints:Pi is roughly 3.142
by controlling the number of decimal places shown. For the purposes of this exercise, uselet pi = 3.141592
as an estimate for pi. (Hint: you may need to check thestd::fmt
documentation for setting the number of decimals to display)
See also:
std::fmt
, macros
, struct
, and traits
1.2.1. Debug
All types which want to use std::fmt
formatting traits
require an implementation to be printable. Automatic implementations are only provided for types such as in the std
library. All others must be manually implemented somehow.
The fmt::Debug
trait
makes this very straightforward. All types can derive
(automatically create) the fmt::Debug
implementation. This is not true for fmt::Display
which must be manually implemented.
// This structure cannot be printed either with `fmt::Display` or
// with `fmt::Debug`.
struct UnPrintable(i32);
// The `derive` attribute automatically creates the implementation
// required to make this `struct` printable with `fmt::Debug`.
#[derive(Debug)]
struct DebugPrintable(i32);
All std
library types are automatically printable with {:?}
too:
// Derive the `fmt::Debug` implementation for `Structure`. `Structure`
// is a structure which contains a single `i32`.
#[derive(Debug)]
struct Structure(i32);
// Put a `Structure` inside of the structure `Deep`. Make it printable
// also.
#[derive(Debug)]
struct Deep(Structure);
fn main() {
// Printing with `{:?}` is similar to with `{}`.
println!("{:?} months in a year.", 12);
println!("{1:?} {0:?} is the {actor:?} name.",
"Slater",
"Christian",
actor="actor's");
// `Structure` is printable!
println!("Now {:?} will print!", Structure(3));
// The problem with `derive` is there is no control over how
// the results look. What if I want this to just show a `7`?
println!("Now {:?} will print!", Deep(Structure(7)));
}
So fmt::Debug
definitely makes this printable but sacrifices some elegance. Rust also provides "pretty printing" with {:#?}
.
#[derive(Debug)]
struct Person<'a> {
name: &'a str,
age: u8
}
fn main() {
let name = "Peter";
let age = 27;
let peter = Person { name, age };
// Pretty print
println!("{:#?}", peter);
}
One can manually implement fmt::Display
to control the display.
See also:
attributes
, derive
, std::fmt
, and struct
1.2.2. Display
fmt::Debug
hardly looks compact and clean, so it is often advantageous to customize the output appearance. This is done by manually implementing fmt::Display
, which uses the {}
print marker. Implementing it looks like this:
// Import (via `use`) the `fmt` module to make it available.
use std::fmt;
// Define a structure for which `fmt::Display` will be implemented. This is
// a tuple struct named `Structure` that contains an `i32`.
struct Structure(i32);
// To use the `{}` marker, the trait `fmt::Display` must be implemented
// manually for the type.
impl fmt::Display for Structure {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
write!(f, "{}", self.0)
}
}
fmt::Display
may be cleaner than fmt::Debug
but this presents a problem for the std
library. How should ambiguous types be displayed? For example, if the std
library implemented a single style for allVec<T>
, what style should it be? Would it be either of these two?
Vec<path>
:/:/etc:/home/username:/bin
(split on:
)Vec<number>
:1,2,3
(split on,
)
No, because there is no ideal style for all types and the std
library doesn't presume to dictate one. fmt::Display
is not implemented for Vec<T>
or for any other generic containers. fmt::Debug
must then be used for these generic cases.
This is not a problem though because for any new container type which is not generic,fmt::Display
can be implemented.
use std::fmt; // Import `fmt`
// A structure holding two numbers. `Debug` will be derived so the results can
// be contrasted with `Display`.
#[derive(Debug)]
struct MinMax(i64, i64);
// Implement `Display` for `MinMax`.
impl fmt::Display for MinMax {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Use `self.number` to refer to each positional data point.
write!(f, "({}, {})", self.0, self.1)
}
}
// Define a structure where the fields are nameable for comparison.
#[derive(Debug)]
struct Point2D {
x: f64,
y: f64,
}
// Similarly, implement `Display` for `Point2D`
impl fmt::Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Customize so only `x` and `y` are denoted.
write!(f, "x: {}, y: {}", self.x, self.y)
}
}
fn main() {
let minmax = MinMax(0, 14);
println!("Compare structures:");
println!("Display: {}", minmax);
println!("Debug: {:?}", minmax);
let big_range = MinMax(-300, 300);
let small_range = MinMax(-3, 3);
println!("The big range is {big} and the small is {small}",
small = small_range,
big = big_range);
let point = Point2D { x: 3.3, y: 7.2 };
println!("Compare points:");
println!("Display: {}", point);
println!("Debug: {:?}", point);
// Error. Both `Debug` and `Display` were implemented, but `{:b}`
// requires `fmt::Binary` to be implemented. This will not work.
// println!("What does Point2D look like in binary: {:b}?", point);
}
So, fmt::Display
has been implemented but fmt::Binary
has not, and therefore cannot be used. std::fmt
has many such traits
and each requires its own implementation. This is detailed further instd::fmt
.
Activity
After checking the output of the above example, use the Point2D
struct as a guide to add a Complex
struct to the example. When printed in the same way, the output should be:
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
See also:
derive
, std::fmt
, macros
, struct
, trait
, and use
1.2.2.1. Testcase: List
Implementing fmt::Display
for a structure where the elements must each be handled sequentially is tricky. The problem is that each write!
generates a fmt::Result
. Proper handling of this requires dealing with all the results. Rust provides the ?
operator for exactly this purpose.
Using ?
on write!
looks like this:
// Try `write!` to see if it errors. If it errors, return
// the error. Otherwise continue.
write!(f, "{}", value)?;
With ?
available, implementing fmt::Display
for a Vec
is
straightforward:
use std::fmt; // Import the `fmt` module.
// Define a structure named `List` containing a `Vec`.
struct List(Vec<i32>);
impl fmt::Display for List {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Extract the value using tuple indexing,
// and create a reference to `vec`.
let vec = &self.0;
write!(f, "[")?;
// Iterate over `v` in `vec` while enumerating the iteration
// count in `count`.
for (count, v) in vec.iter().enumerate() {
// For every element except the first, add a comma.
// Use the ? operator to return on errors.
if count != 0 { write!(f, ", ")?; }
write!(f, "{}", v)?;
}
// Close the opened bracket and return a fmt::Result value.
write!(f, "]")
}
}
fn main() {
let v = List(vec![1, 2, 3]);
println!("{}", v);
}
Activity
Try changing the program so that the index of each element in the vector is also printed. The new output should look like this:
[0: 1, 1: 2, 2: 3]
See also:
for
, ref
, Result
, struct
, ?
, and vec!
1.2.3. Formatting
We've seen that formatting is specified via a format string:
format!("{}", foo)
->"3735928559"
format!("0x{:X}", foo)
->"0xDEADBEEF"
format!("0o{:o}", foo)
->"0o33653337357"
The same variable (foo
) can be formatted differently depending on which argument type is used: X
vs o
vs unspecified.
This formatting functionality is implemented via traits, and there is one trait for each argument type. The most common formatting trait is Display
, which handles cases where the argument type is left unspecified: {}
for instance.
use std::fmt::{self, Formatter, Display};
struct City {
name: &'static str,
// Latitude
lat: f32,
// Longitude
lon: f32,
}
impl Display for City {
// `f` is a buffer, and this method must write the formatted string into it
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' };
let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' };
// `write!` is like `format!`, but it will write the formatted string
// into a buffer (the first argument)
write!(f, "{}: {:.3}°{} {:.3}°{}",
self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c)
}
}
#[derive(Debug)]
struct Color {
red: u8,
green: u8,
blue: u8,
}
fn main() {
for city in [
City { name: "Dublin", lat: 53.347778, lon: -6.259722 },
City { name: "Oslo", lat: 59.95, lon: 10.75 },
City { name: "Vancouver", lat: 49.25, lon: -123.1 },
].iter() {
println!("{}", *city);
}
for color in [
Color { red: 128, green: 255, blue: 90 },
Color { red: 0, green: 3, blue: 254 },
Color { red: 0, green: 0, blue: 0 },
].iter() {
// Switch this to use {} once you've added an implementation
// for fmt::Display.
println!("{:?}", *color);
}
}
You can view a full list of formatting traits and their argument types in the std::fmt
documentation.
Activity
Add an implementation of the fmt::Display
trait for the Color
struct above so that the output displays as:
RGB (128, 255, 90) 0x80FF5A
RGB (0, 3, 254) 0x0003FE
RGB (0, 0, 0) 0x000000
Two hints if you get stuck:
- You may need to list each color more than once,
- You can pad with zeros to a width of 2 with
:02
.