1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-04-08 17:03:53 +02:00

Format all Markdown files with dprint ()

This is the result of running `dprint fmt` after removing `src/` from
the list of excluded directories.

This also reformats the Rust code: we might want to tweak this a bit in
the future since some of the changes removes the hand-formatting. Of
course, this formatting can be seen as a mis-feature, so maybe this is
good overall.

Thanks to mdbook-i18n-helpers 0.2, the POT file is nearly unchanged
after this, meaning that all existing translations remain valid! A few
messages were changed because of stray whitespace characters:

     msgid ""
     "Slices always borrow from another object. In this example, `a` has to remain "
    -"'alive' (in scope) for at least as long as our slice. "
    +"'alive' (in scope) for at least as long as our slice."
     msgstr ""

The formatting is enforced in CI and we will have to see how annoying
this is in practice for the many contributors. If it becomes annoying,
we should look into fixing  so that `dprint` can annotate
the lines that need fixing directly, then I think we can consider more
strict formatting checks.

I added more customization to `rustfmt.toml`. This is to better emulate
the dense style used in the course:

- `max_width = 85` allows lines to take up the full width available in
our code blocks (when taking margins and the line numbers into account).
- `wrap_comments = true` ensures that we don't show very long comments
in the code examples. I edited some comments to shorten them and avoid
unnecessary line breaks — please trim other unnecessarily long comments
when you see them! Remember we're writing code for slides 😄
- `use_small_heuristics = "Max"` allows for things like struct literals
and if-statements to take up the full line width configured above.

The formatting settings apply to all our Rust code right now — I think
we could improve this with https://github.com/dprint/dprint/issues/711
which lets us add per-directory `dprint` configuration files. However,
the `inherit: true` setting is not yet implemented (as far as I can
tell), so a nested configuration file will have to copy most or all of
the top-level file.
This commit is contained in:
Martin Geisler 2023-12-31 00:15:07 +01:00 committed by GitHub
parent f43e72e0ad
commit c9f66fd425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
302 changed files with 3067 additions and 2622 deletions

@ -18,7 +18,6 @@
}, },
"excludes": [ "excludes": [
"/book/", "/book/",
"/src/",
"/theme/*.hbs", "/theme/*.hbs",
"/third_party/", "/third_party/",
"target/" "target/"

@ -22,16 +22,18 @@
//! Slide -- a single topic (may be represented by multiple mdBook chapters) //! Slide -- a single topic (may be represented by multiple mdBook chapters)
//! ``` //! ```
//! //!
//! This structure is parsed from the format of the book using a combination of the order in which //! This structure is parsed from the format of the book using a combination of
//! chapters are listed in `SUMMARY.md` and annotations in the frontmatter of each chapter. //! the order in which chapters are listed in `SUMMARY.md` and annotations in
//! the frontmatter of each chapter.
//! //!
//! A book contains a sequence of BookItems, each of which can contain sub-items. A top-level item //! A book contains a sequence of BookItems, each of which can contain
//! can potentially introduce a new course, session, segment, and slide all in the same item. If //! sub-items. A top-level item can potentially introduce a new course, session,
//! the item has a `course` property in its frontmatter, then it introduces a new course. If it has //! segment, and slide all in the same item. If the item has a `course` property
//! a `session` property, then it introduces a new session. A top-level item always corresponds //! in its frontmatter, then it introduces a new course. If it has a `session`
//! 1-to-1 with a segment (as long as it is a chapter), and that item becomes the first slide in //! property, then it introduces a new session. A top-level item always
//! that segment. Any other sub-items of the top-level item are treated as further slides in the //! corresponds 1-to-1 with a segment (as long as it is a chapter), and that
//! same segment. //! item becomes the first slide in that segment. Any other sub-items of the
//! top-level item are treated as further slides in the same segment.
use crate::frontmatter::{split_frontmatter, Frontmatter}; use crate::frontmatter::{split_frontmatter, Frontmatter};
use crate::markdown::{duration, relative_link}; use crate::markdown::{duration, relative_link};
@ -53,19 +55,19 @@ pub struct Courses {
/// A Course is the level of content at which students enroll. /// A Course is the level of content at which students enroll.
/// ///
/// Courses are identified by the `course` property in a session's frontmatter. All /// Courses are identified by the `course` property in a session's frontmatter.
/// sessions with the same value for `course` are grouped into a Course. /// All sessions with the same value for `course` are grouped into a Course.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Course { pub struct Course {
pub name: String, pub name: String,
pub sessions: Vec<Session>, pub sessions: Vec<Session>,
} }
/// A Session is a block of instructional time, containing segments. Typically a full day of /// A Session is a block of instructional time, containing segments. Typically a
/// instruction contains two sessions: morning and afternoon. /// full day of instruction contains two sessions: morning and afternoon.
/// ///
/// A session is identified by the `session` property in the session's frontmatter. There can be /// A session is identified by the `session` property in the session's
/// only one session with a given name in a course. /// frontmatter. There can be only one session with a given name in a course.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Session { pub struct Session {
pub name: String, pub name: String,
@ -95,8 +97,8 @@ pub struct Slide {
} }
impl Courses { impl Courses {
/// Extract the course structure from the book. As a side-effect, the frontmatter is stripped /// Extract the course structure from the book. As a side-effect, the
/// from each slide. /// frontmatter is stripped from each slide.
pub fn extract_structure(mut book: Book) -> anyhow::Result<(Self, Book)> { pub fn extract_structure(mut book: Book) -> anyhow::Result<(Self, Book)> {
let mut courses = Courses::default(); let mut courses = Courses::default();
let mut current_course_name = None; let mut current_course_name = None;
@ -111,7 +113,8 @@ impl Courses {
let (frontmatter, content) = split_frontmatter(chapter)?; let (frontmatter, content) = split_frontmatter(chapter)?;
chapter.content = content; chapter.content = content;
// If 'course' is given, use that course (if not 'none') and reset the session. // If 'course' is given, use that course (if not 'none') and reset the
// session.
if let Some(course_name) = &frontmatter.course { if let Some(course_name) = &frontmatter.course {
current_session_name = None; current_session_name = None;
if course_name == "none" { if course_name == "none" {
@ -133,7 +136,8 @@ impl Courses {
); );
} }
// If we have a course and session, then add this chapter to it as a segment. // If we have a course and session, then add this chapter to it as a
// segment.
if let (Some(course_name), Some(session_name)) = if let (Some(course_name), Some(session_name)) =
(&current_course_name, &current_session_name) (&current_course_name, &current_session_name)
{ {
@ -145,7 +149,8 @@ impl Courses {
Ok((courses, book)) Ok((courses, book))
} }
/// Get a reference to a course, adding a new one if none by this name exists. /// Get a reference to a course, adding a new one if none by this name
/// exists.
fn course_mut(&mut self, name: impl AsRef<str>) -> &mut Course { fn course_mut(&mut self, name: impl AsRef<str>) -> &mut Course {
let name = name.as_ref(); let name = name.as_ref();
if let Some(found_idx) = if let Some(found_idx) =
@ -164,8 +169,8 @@ impl Courses {
self.courses.iter().find(|c| c.name == name) self.courses.iter().find(|c| c.name == name)
} }
/// Find the slide generated from the given Chapter within these courses, returning the "path" /// Find the slide generated from the given Chapter within these courses,
/// to that slide. /// returning the "path" to that slide.
pub fn find_slide( pub fn find_slide(
&self, &self,
chapter: &Chapter, chapter: &Chapter,
@ -201,19 +206,15 @@ impl<'a> IntoIterator for &'a Courses {
impl Course { impl Course {
fn new(name: impl Into<String>) -> Self { fn new(name: impl Into<String>) -> Self {
Course { Course { name: name.into(), ..Default::default() }
name: name.into(),
..Default::default()
}
} }
/// Get a reference to a session, adding a new one if none by this name exists. /// Get a reference to a session, adding a new one if none by this name
/// exists.
fn session_mut(&mut self, name: impl AsRef<str>) -> &mut Session { fn session_mut(&mut self, name: impl AsRef<str>) -> &mut Session {
let name = name.as_ref(); let name = name.as_ref();
if let Some(found_idx) = self if let Some(found_idx) =
.sessions self.sessions.iter().position(|session| &session.name == name)
.iter()
.position(|session| &session.name == name)
{ {
return &mut self.sessions[found_idx]; return &mut self.sessions[found_idx];
} }
@ -222,15 +223,17 @@ impl Course {
self.sessions.last_mut().unwrap() self.sessions.last_mut().unwrap()
} }
/// Return the total duration of this course, as the sum of all segment durations. /// Return the total duration of this course, as the sum of all segment
/// durations.
/// ///
/// This includes breaks between segments, but does not count time between between /// This includes breaks between segments, but does not count time between
/// sessions. /// between sessions.
pub fn minutes(&self) -> u64 { pub fn minutes(&self) -> u64 {
self.into_iter().map(|s| s.minutes()).sum() self.into_iter().map(|s| s.minutes()).sum()
} }
/// Generate a Markdown schedule for this course, for placement at the given path. /// Generate a Markdown schedule for this course, for placement at the given
/// path.
pub fn schedule(&self, at_source_path: impl AsRef<Path>) -> String { pub fn schedule(&self, at_source_path: impl AsRef<Path>) -> String {
let mut outline = String::from("Course schedule:\n"); let mut outline = String::from("Course schedule:\n");
for session in self { for session in self {
@ -249,7 +252,10 @@ impl Course {
&mut outline, &mut outline,
" * [{}]({}) ({})", " * [{}]({}) ({})",
segment.name, segment.name,
relative_link(&at_source_path, &segment.slides[0].source_paths[0]), relative_link(
&at_source_path,
&segment.slides[0].source_paths[0]
),
duration(segment.minutes()) duration(segment.minutes())
) )
.unwrap(); .unwrap();
@ -270,10 +276,7 @@ impl<'a> IntoIterator for &'a Course {
impl Session { impl Session {
fn new(name: impl Into<String>) -> Self { fn new(name: impl Into<String>) -> Self {
Session { Session { name: name.into(), ..Default::default() }
name: name.into(),
..Default::default()
}
} }
/// Add a new segment to the session, representing sub-items as slides. /// Add a new segment to the session, representing sub-items as slides.
@ -297,7 +300,8 @@ impl Session {
Ok(()) Ok(())
} }
/// Generate a Markdown outline for this session, for placement at the given path. /// Generate a Markdown outline for this session, for placement at the given
/// path.
pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String { pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String {
let mut outline = String::from("In this session:\n"); let mut outline = String::from("In this session:\n");
for segment in self { for segment in self {
@ -324,7 +328,8 @@ impl Session {
if instructional_time == 0 { if instructional_time == 0 {
return instructional_time; return instructional_time;
} }
let breaks = (self.into_iter().filter(|s| s.minutes() > 0).count() - 1) as u64 let breaks = (self.into_iter().filter(|s| s.minutes() > 0).count() - 1)
as u64
* BREAK_DURATION; * BREAK_DURATION;
instructional_time + breaks instructional_time + breaks
} }
@ -341,14 +346,11 @@ impl<'a> IntoIterator for &'a Session {
impl Segment { impl Segment {
fn new(name: impl Into<String>) -> Self { fn new(name: impl Into<String>) -> Self {
Segment { Segment { name: name.into(), ..Default::default() }
name: name.into(),
..Default::default()
}
} }
/// Create a slide from a chapter. If `recurse` is true, sub-items of this chapter are /// Create a slide from a chapter. If `recurse` is true, sub-items of this
/// included in this slide as well. /// chapter are included in this slide as well.
fn add_slide( fn add_slide(
&mut self, &mut self,
frontmatter: Frontmatter, frontmatter: Frontmatter,
@ -364,8 +366,8 @@ impl Segment {
Ok(()) Ok(())
} }
/// Return the total duration of this segment (the sum of the durations of the enclosed /// Return the total duration of this segment (the sum of the durations of
/// slides). /// the enclosed slides).
pub fn minutes(&self) -> u64 { pub fn minutes(&self) -> u64 {
self.into_iter().map(|s| s.minutes()).sum() self.into_iter().map(|s| s.minutes()).sum()
} }
@ -406,10 +408,7 @@ impl<'a> IntoIterator for &'a Segment {
impl Slide { impl Slide {
fn new(frontmatter: Frontmatter, chapter: &Chapter) -> Self { fn new(frontmatter: Frontmatter, chapter: &Chapter) -> Self {
let mut slide = Self { let mut slide = Self { name: chapter.name.clone(), ..Default::default() };
name: chapter.name.clone(),
..Default::default()
};
slide.add_frontmatter(&frontmatter); slide.add_frontmatter(&frontmatter);
slide.push_source_path(&chapter.source_path); slide.push_source_path(&chapter.source_path);
slide slide

@ -25,10 +25,12 @@ pub struct Frontmatter {
} }
/// Split a chapter's contents into frontmatter and the remaining contents. /// Split a chapter's contents into frontmatter and the remaining contents.
pub fn split_frontmatter(chapter: &Chapter) -> anyhow::Result<(Frontmatter, String)> { pub fn split_frontmatter(
chapter: &Chapter,
) -> anyhow::Result<(Frontmatter, String)> {
if let Some((frontmatter, content)) = matter(&chapter.content) { if let Some((frontmatter, content)) = matter(&chapter.content) {
let frontmatter: Frontmatter = let frontmatter: Frontmatter = serde_yaml::from_str(&frontmatter)
serde_yaml::from_str(&frontmatter).with_context(|| { .with_context(|| {
format!("error parsing frontmatter in {:?}", chapter.source_path) format!("error parsing frontmatter in {:?}", chapter.source_path)
})?; })?;

@ -30,7 +30,9 @@ fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
let app = Command::new("mdbook-course") let app = Command::new("mdbook-course")
.about("mdbook preprocessor for Comprehensive Rust") .about("mdbook preprocessor for Comprehensive Rust")
.subcommand(Command::new("supports").arg(Arg::new("renderer").required(true))); .subcommand(
Command::new("supports").arg(Arg::new("renderer").required(true)),
);
let matches = app.get_matches(); let matches = app.get_matches();
if let Some(_) = matches.subcommand_matches("supports") { if let Some(_) = matches.subcommand_matches("supports") {
@ -65,18 +67,11 @@ fn timediff(actual: u64, target: u64) -> String {
} }
fn print_summary(fundamentals: &Course) { fn print_summary(fundamentals: &Course) {
eprintln!( eprintln!("Fundamentals: {}", timediff(fundamentals.minutes(), 8 * 3 * 60));
"Fundamentals: {}",
timediff(fundamentals.minutes(), 8 * 3 * 60)
);
eprintln!("Sessions:"); eprintln!("Sessions:");
for session in fundamentals { for session in fundamentals {
eprintln!( eprintln!(" {}: {}", session.name, timediff(session.minutes(), 3 * 60));
" {}: {}",
session.name,
timediff(session.minutes(), 3 * 60)
);
for segment in session { for segment in session {
eprintln!(" {}: {}", segment.name, duration(segment.minutes())); eprintln!(" {}: {}", segment.name, duration(segment.minutes()));
} }
@ -95,7 +90,9 @@ fn preprocess() -> anyhow::Result<()> {
book.for_each_mut(|chapter| { book.for_each_mut(|chapter| {
if let BookItem::Chapter(chapter) = chapter { if let BookItem::Chapter(chapter) = chapter {
if let Some((course, session, segment, slide)) = courses.find_slide(chapter) { if let Some((course, session, segment, slide)) =
courses.find_slide(chapter)
{
timing_info::insert_timing_info(slide, chapter); timing_info::insert_timing_info(slide, chapter);
replacements::replace( replacements::replace(
&courses, &courses,

@ -14,8 +14,8 @@
use std::path::Path; use std::path::Path;
/// Given a source_path for the markdown file being rendered and a source_path for the target, /// Given a source_path for the markdown file being rendered and a source_path
/// generate a relative link. /// for the target, generate a relative link.
pub fn relative_link( pub fn relative_link(
doc_path: impl AsRef<Path>, doc_path: impl AsRef<Path>,
target_path: impl AsRef<Path>, target_path: impl AsRef<Path>,
@ -72,7 +72,10 @@ mod test {
#[test] #[test]
fn relative_link_subdir() { fn relative_link_subdir() {
assert_eq!( assert_eq!(
relative_link(Path::new("hello-world.md"), Path::new("hello-world/foo.md")), relative_link(
Path::new("hello-world.md"),
Path::new("hello-world/foo.md")
),
"./hello-world/foo.md".to_string() "./hello-world/foo.md".to_string()
); );
} }
@ -80,7 +83,10 @@ mod test {
#[test] #[test]
fn relative_link_parent_dir() { fn relative_link_parent_dir() {
assert_eq!( assert_eq!(
relative_link(Path::new("references/foo.md"), Path::new("hello-world.md")), relative_link(
Path::new("references/foo.md"),
Path::new("hello-world.md")
),
"../hello-world.md".to_string() "../hello-world.md".to_string()
); );
} }

@ -23,11 +23,7 @@ pub fn insert_timing_info(slide: &Slide, chapter: &mut Chapter) {
{ {
// Include the minutes in the speaker notes. // Include the minutes in the speaker notes.
let minutes = slide.minutes; let minutes = slide.minutes;
let plural = if slide.minutes == 1 { let plural = if slide.minutes == 1 { "minute" } else { "minutes" };
"minute"
} else {
"minutes"
};
let mut subslides = ""; let mut subslides = "";
if slide.source_paths.len() > 1 { if slide.source_paths.len() > 1 {
subslides = "and its sub-slides "; subslides = "and its sub-slides ";

@ -24,10 +24,10 @@ const FILENAME_END: &str = " -->";
pub fn process(output_directory: &Path, input_contents: &str) -> anyhow::Result<()> { pub fn process(output_directory: &Path, input_contents: &str) -> anyhow::Result<()> {
let parser = Parser::new(input_contents); let parser = Parser::new(input_contents);
// Find a specially-formatted comment followed by a code block, and then call `write_output` // Find a specially-formatted comment followed by a code block, and then call
// with the contents of the code block, to write to a file named by the comment. Code blocks // `write_output` with the contents of the code block, to write to a file
// without matching comments will be ignored, as will comments which are not followed by a code // named by the comment. Code blocks without matching comments will be
// block. // ignored, as will comments which are not followed by a code block.
let mut next_filename: Option<String> = None; let mut next_filename: Option<String> = None;
let mut current_file: Option<File> = None; let mut current_file: Option<File> = None;
for event in parser { for event in parser {

@ -35,7 +35,9 @@ fn main() -> anyhow::Result<()> {
let output_directory = Path::new( let output_directory = Path::new(
config config
.get("output-directory") .get("output-directory")
.context("Missing output.exerciser.output-directory configuration value")? .context(
"Missing output.exerciser.output-directory configuration value",
)?
.as_str() .as_str()
.context("Expected a string for output.exerciser.output-directory")?, .context("Expected a string for output.exerciser.output-directory")?,
); );
@ -55,8 +57,8 @@ fn process_all(book: &Book, output_directory: &Path) -> anyhow::Result<()> {
if let BookItem::Chapter(chapter) = item { if let BookItem::Chapter(chapter) = item {
trace!("Chapter {:?} / {:?}", chapter.path, chapter.source_path); trace!("Chapter {:?} / {:?}", chapter.path, chapter.source_path);
if let Some(chapter_path) = &chapter.path { if let Some(chapter_path) = &chapter.path {
// Put the exercises in a subdirectory named after the chapter file, without its // Put the exercises in a subdirectory named after the chapter file,
// parent directories. // without its parent directories.
let chapter_output_directory = let chapter_output_directory =
output_directory.join(chapter_path.file_stem().with_context( output_directory.join(chapter_path.file_stem().with_context(
|| format!("Chapter {:?} has no file stem", chapter_path), || format!("Chapter {:?} has no file stem", chapter_path),

@ -10081,7 +10081,7 @@ msgstr "\"Falha ao ler\""
#: src/error-handling/error-contexts.md:19 #: src/error-handling/error-contexts.md:19
msgid "\"Found no username in {path}\"" msgid "\"Found no username in {path}\""
msgstr "\"Nome de usuário não encontrado em {caminho}\"" msgstr "\"Nome de usuário não encontrado em {path}\""
#: src/error-handling/error-contexts.md:28 #: src/error-handling/error-contexts.md:28
msgid "\"Error: {err:?}\"" msgid "\"Error: {err:?}\""

@ -1,2 +1,8 @@
imports_granularity = "module" # Please use nightly for this setting. # Please use a nightly rustfmt for these settings.
max_width = 90 imports_granularity = "module"
wrap_comments = true
# The code blocks get a scrollbar if they are wider than this.
max_width = 85
# Allow all constructs to take up max_width columns.
use_small_heuristics = "Max"

@ -1,6 +1,7 @@
<!-- Keep first page as index.md to avoid giving it two names. --> <!-- Keep first page as index.md to avoid giving it two names. -->
[Welcome to Comprehensive Rust 🦀](index.md) [Welcome to Comprehensive Rust 🦀](index.md)
- [Running the Course](running-the-course.md) - [Running the Course](running-the-course.md)
- [Course Structure](running-the-course/course-structure.md) - [Course Structure](running-the-course/course-structure.md)
- [Keyboard Shortcuts](running-the-course/keyboard-shortcuts.md) - [Keyboard Shortcuts](running-the-course/keyboard-shortcuts.md)
@ -10,8 +11,7 @@
- [Code Samples](cargo/code-samples.md) - [Code Samples](cargo/code-samples.md)
- [Running Cargo Locally](cargo/running-locally.md) - [Running Cargo Locally](cargo/running-locally.md)
---
----
# Day 1: Morning # Day 1: Morning
@ -63,7 +63,7 @@
- [Exercise: Elevator Events](user-defined-types/exercise.md) - [Exercise: Elevator Events](user-defined-types/exercise.md)
- [Solution](user-defined-types/solution.md) - [Solution](user-defined-types/solution.md)
---- ---
# Day 2: Morning # Day 2: Morning
@ -112,7 +112,7 @@
- [Exercise: ROT13](std-traits/exercise.md) - [Exercise: ROT13](std-traits/exercise.md)
- [Solution](std-traits/solution.md) - [Solution](std-traits/solution.md)
---- ---
# Day 3: Morning # Day 3: Morning
@ -202,7 +202,7 @@
# Android # Android
---- ---
- [Welcome](android.md) - [Welcome](android.md)
- [Setup](android/setup.md) - [Setup](android/setup.md)
@ -237,11 +237,9 @@
- [With Java](android/interoperability/java.md) - [With Java](android/interoperability/java.md)
- [Exercises](exercises/android/morning.md) - [Exercises](exercises/android/morning.md)
# Chromium # Chromium
---- ---
- [Welcome](chromium.md) - [Welcome](chromium.md)
- [Setup](chromium/setup.md) - [Setup](chromium/setup.md)
@ -281,10 +279,9 @@
- [Bringing It Together - Exercise](exercises/chromium/bringing-it-together.md) - [Bringing It Together - Exercise](exercises/chromium/bringing-it-together.md)
- [Exercise Solutions](exercises/chromium/solutions.md) - [Exercise Solutions](exercises/chromium/solutions.md)
# Bare Metal: Morning # Bare Metal: Morning
---- ---
- [Welcome](bare-metal.md) - [Welcome](bare-metal.md)
- [`no_std`](bare-metal/no_std.md) - [`no_std`](bare-metal/no_std.md)
@ -333,10 +330,9 @@
- [RTC Driver](exercises/bare-metal/rtc.md) - [RTC Driver](exercises/bare-metal/rtc.md)
- [Solutions](exercises/bare-metal/solutions-afternoon.md) - [Solutions](exercises/bare-metal/solutions-afternoon.md)
# Concurrency: Morning # Concurrency: Morning
---- ---
- [Welcome](concurrency.md) - [Welcome](concurrency.md)
- [Threads](concurrency/threads.md) - [Threads](concurrency/threads.md)
@ -379,10 +375,9 @@
- [Broadcast Chat Application](exercises/concurrency/chat-app.md) - [Broadcast Chat Application](exercises/concurrency/chat-app.md)
- [Solutions](exercises/concurrency/solutions-afternoon.md) - [Solutions](exercises/concurrency/solutions-afternoon.md)
# Final Words # Final Words
---- ---
- [Thanks!](thanks.md) - [Thanks!](thanks.md)
- [Glossary](glossary.md) - [Glossary](glossary.md)

@ -2,11 +2,12 @@
course: Android course: Android
session: Android session: Android
--- ---
# Welcome to Rust in Android # Welcome to Rust in Android
Rust is supported for system software on Android. This means that Rust is supported for system software on Android. This means that you can write
you can write new services, libraries, drivers or even firmware in Rust new services, libraries, drivers or even firmware in Rust (or improve existing
(or improve existing code as needed). code as needed).
> We will attempt to call Rust from one of your own projects today. So try to > We will attempt to call Rust from one of your own projects today. So try to
> find a little corner of your code base where we can move some lines of code to > find a little corner of your code base where we can move some lines of code to
@ -15,15 +16,19 @@ you can write new services, libraries, drivers or even firmware in Rust
<details> <details>
The speaker may mention any of the following given the increased use of Rust The speaker may mention any of the following given the increased use of Rust in
in Android: Android:
- Service example: [DNS over HTTP](https://security.googleblog.com/2022/07/dns-over-http3-in-android.html) - Service example:
[DNS over HTTP](https://security.googleblog.com/2022/07/dns-over-http3-in-android.html)
- Libraries: [Rutabaga Virtual Graphics Interface](https://crosvm.dev/book/appendix/rutabaga_gfx.html) - Libraries:
[Rutabaga Virtual Graphics Interface](https://crosvm.dev/book/appendix/rutabaga_gfx.html)
- Kernel Drivers: [Binder](https://lore.kernel.org/rust-for-linux/20231101-rust-binder-v1-0-08ba9197f637@google.com/) - Kernel Drivers:
[Binder](https://lore.kernel.org/rust-for-linux/20231101-rust-binder-v1-0-08ba9197f637@google.com/)
- Firmware: [pKVM firmware](https://security.googleblog.com/2023/10/bare-metal-rust-in-android.html) - Firmware:
[pKVM firmware](https://security.googleblog.com/2023/10/bare-metal-rust-in-android.html)
</details> </details>

@ -1,7 +1,9 @@
# AIDL # AIDL
The [Android Interface Definition Language The
(AIDL)](https://developer.android.com/guide/components/aidl) is supported in Rust: [Android Interface Definition Language
(AIDL)](https://developer.android.com/guide/components/aidl) is supported in
Rust:
* Rust code can call existing AIDL servers, - Rust code can call existing AIDL servers,
* You can create new AIDL servers in Rust. - You can create new AIDL servers in Rust.

@ -20,15 +20,14 @@ use com_example_birthdayservice::binder;
const SERVICE_IDENTIFIER: &str = "birthdayservice"; const SERVICE_IDENTIFIER: &str = "birthdayservice";
/// Connect to the BirthdayService. /// Connect to the BirthdayService.
pub fn connect() -> Result<binder::Strong<dyn IBirthdayService>, binder::StatusCode> { pub fn connect() -> Result<binder::Strong<dyn IBirthdayService>, binder::StatusCode>
{
binder::get_interface(SERVICE_IDENTIFIER) binder::get_interface(SERVICE_IDENTIFIER)
} }
/// Call the birthday service. /// Call the birthday service.
fn main() -> Result<(), binder::Status> { fn main() -> Result<(), binder::Status> {
let name = std::env::args() let name = std::env::args().nth(1).unwrap_or_else(|| String::from("Bob"));
.nth(1)
.unwrap_or_else(|| String::from("Bob"));
let years = std::env::args() let years = std::env::args()
.nth(2) .nth(2)
.and_then(|arg| arg.parse::<i32>().ok()) .and_then(|arg| arg.parse::<i32>().ok())

@ -24,8 +24,6 @@ impl binder::Interface for BirthdayService {}
impl IBirthdayService for BirthdayService { impl IBirthdayService for BirthdayService {
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> { fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> {
Ok(format!( Ok(format!("Happy Birthday {name}, congratulations with the {years} years!"))
"Happy Birthday {name}, congratulations with the {years} years!"
))
} }
} }

@ -2,13 +2,13 @@
Finally, we can create a Rust client for our new service. Finally, we can create a Rust client for our new service.
*birthday_service/src/client.rs*: _birthday_service/src/client.rs_:
```rust,ignore ```rust,ignore
{{#include birthday_service/src/client.rs:main}} {{#include birthday_service/src/client.rs:main}}
``` ```
*birthday_service/Android.bp*: _birthday_service/Android.bp_:
```javascript ```javascript
{{#include birthday_service/Android.bp:birthday_client}} {{#include birthday_service/Android.bp:birthday_client}}

@ -2,13 +2,13 @@
We can now implement the AIDL service: We can now implement the AIDL service:
*birthday_service/src/lib.rs*: _birthday_service/src/lib.rs_:
```rust,ignore ```rust,ignore
{{#include birthday_service/src/lib.rs:IBirthdayService}} {{#include birthday_service/src/lib.rs:IBirthdayService}}
``` ```
*birthday_service/Android.bp*: _birthday_service/Android.bp_:
```javascript ```javascript
{{#include birthday_service/Android.bp:libbirthdayservice}} {{#include birthday_service/Android.bp:libbirthdayservice}}

@ -2,13 +2,13 @@
You declare the API of your service using an AIDL interface: You declare the API of your service using an AIDL interface:
*birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl*: _birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl_:
```java ```java
{{#include birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}} {{#include birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:IBirthdayService}}
``` ```
*birthday_service/aidl/Android.bp*: _birthday_service/aidl/Android.bp_:
```javascript ```javascript
{{#include birthday_service/aidl/Android.bp}} {{#include birthday_service/aidl/Android.bp}}

@ -2,13 +2,13 @@
Finally, we can create a server which exposes the service: Finally, we can create a server which exposes the service:
*birthday_service/src/server.rs*: _birthday_service/src/server.rs_:
```rust,ignore ```rust,ignore
{{#include birthday_service/src/server.rs:main}} {{#include birthday_service/src/server.rs:main}}
``` ```
*birthday_service/Android.bp*: _birthday_service/Android.bp_:
```javascript ```javascript
{{#include birthday_service/Android.bp:birthday_server}} {{#include birthday_service/Android.bp:birthday_server}}

@ -19,13 +19,20 @@ We will look at `rust_binary` and `rust_library` next.
Additional items speaker may mention: Additional items speaker may mention:
- Cargo is not optimized for multi-language repos, and also downloads packages from the internet. - Cargo is not optimized for multi-language repos, and also downloads packages
from the internet.
- For compliance and performance, Android must have crates in-tree. It must also interop with C/C++/Java code. Soong fills that gap. - For compliance and performance, Android must have crates in-tree. It must also
interop with C/C++/Java code. Soong fills that gap.
- Soong has many similarities to Bazel, which is the open-source variant of Blaze (used in google3). - Soong has many similarities to Bazel, which is the open-source variant of
Blaze (used in google3).
- There is a plan to transition [Android](https://source.android.com/docs/setup/build/bazel/introduction), [ChromeOS](https://chromium.googlesource.com/chromiumos/bazel/), and [Fuchsia](https://source.android.com/docs/setup/build/bazel/introduction) to Bazel. - There is a plan to transition
[Android](https://source.android.com/docs/setup/build/bazel/introduction),
[ChromeOS](https://chromium.googlesource.com/chromiumos/bazel/), and
[Fuchsia](https://source.android.com/docs/setup/build/bazel/introduction) to
Bazel.
- Learning Bazel-like build rules is useful for all Rust OS developers. - Learning Bazel-like build rules is useful for all Rust OS developers.

@ -4,8 +4,8 @@ You use `rust_library` to create a new Rust library for Android.
Here we declare a dependency on two libraries: Here we declare a dependency on two libraries:
* `libgreeting`, which we define below, - `libgreeting`, which we define below,
* `libtextwrap`, which is a crate already vendored in - `libtextwrap`, which is a crate already vendored in
[`external/rust/crates/`][crates]. [`external/rust/crates/`][crates].
[crates]: https://cs.android.com/android/platform/superproject/+/master:external/rust/crates/ [crates]: https://cs.android.com/android/platform/superproject/+/master:external/rust/crates/

@ -3,8 +3,8 @@
Rust has excellent support for interoperability with other languages. This means Rust has excellent support for interoperability with other languages. This means
that you can: that you can:
* Call Rust functions from other languages. - Call Rust functions from other languages.
* Call functions written in other languages from Rust. - Call functions written in other languages from Rust.
When you call functions in a foreign language we say that you're using a When you call functions in a foreign language we say that you're using a
_foreign function interface_, also known as FFI. _foreign function interface_, also known as FFI.

@ -17,12 +17,12 @@ cc_library_static {
<details> <details>
* Point out that `libcxx_test_bridge_header` and `libcxx_test_bridge_code` are - Point out that `libcxx_test_bridge_header` and `libcxx_test_bridge_code` are
the dependencies for the CXX-generated C++ bindings. We'll show how these are the dependencies for the CXX-generated C++ bindings. We'll show how these are
setup on the next slide. setup on the next slide.
* Note that you also need to depend on the `cxx-bridge-header` library in order - Note that you also need to depend on the `cxx-bridge-header` library in order
to pull in common CXX definitions. to pull in common CXX definitions.
* Full docs for using CXX in Android can be found in [the Android docs]. You may - Full docs for using CXX in Android can be found in [the Android docs]. You may
want to share that link with the class so that students know where they can want to share that link with the class so that students know where they can
find these instructions again in the future. find these instructions again in the future.

@ -26,9 +26,9 @@ genrule {
<details> <details>
* The `cxxbridge` tool is a standalone tool that generates the C++ side of the - The `cxxbridge` tool is a standalone tool that generates the C++ side of the
bridge module. It is included in Android and available as a Soong tool. bridge module. It is included in Android and available as a Soong tool.
* By convention, if your Rust source file is `lib.rs` your header file will be - By convention, if your Rust source file is `lib.rs` your header file will be
named `lib.rs.h` and your source file will be named `lib.rs.cc`. This naming named `lib.rs.h` and your source file will be named `lib.rs.cc`. This naming
convention isn't enforced, though. convention isn't enforced, though.

@ -10,14 +10,14 @@ a Rust module annotated with the `#[cxx::bridge]` attribute macro.
<details> <details>
* The bridge is generally declared in an `ffi` module within your crate. - The bridge is generally declared in an `ffi` module within your crate.
* From the declarations made in the bridge module, CXX will generate matching - From the declarations made in the bridge module, CXX will generate matching
Rust and C++ type/function definitions in order to expose those items to both Rust and C++ type/function definitions in order to expose those items to both
languages. languages.
* To view the generated Rust code, use [cargo-expand] to view the expanded proc - To view the generated Rust code, use [cargo-expand] to view the expanded proc
macro. For most of the examples you would use `cargo expand ::ffi` to expand macro. For most of the examples you would use `cargo expand ::ffi` to expand
just the `ffi` module (though this doesn't apply for Android projects). just the `ffi` module (though this doesn't apply for Android projects).
* To view the generated C++ code, look in `target/cxxbridge`. - To view the generated C++ code, look in `target/cxxbridge`.
[cargo-expand]: https://github.com/dtolnay/cargo-expand [cargo-expand]: https://github.com/dtolnay/cargo-expand

@ -43,10 +43,10 @@ impl BlobstoreClient {
<details> <details>
* The programmer does not need to promise that the signatures they have typed in - The programmer does not need to promise that the signatures they have typed in
are accurate. CXX performs static assertions that the signatures exactly are accurate. CXX performs static assertions that the signatures exactly
correspond with what is declared in C++. correspond with what is declared in C++.
* `unsafe extern` blocks allow you to declare C++ functions that are safe to - `unsafe extern` blocks allow you to declare C++ functions that are safe to
call from Rust. call from Rust.
</details> </details>

@ -6,9 +6,9 @@
<details> <details>
* C++ functions declared to return a `Result` will catch any thrown exception on - C++ functions declared to return a `Result` will catch any thrown exception on
the C++ side and return it as an `Err` value to the calling Rust function. the C++ side and return it as an `Err` value to the calling Rust function.
* If an exception is thrown from an extern "C++" function that is not declared - If an exception is thrown from an extern "C++" function that is not declared
by the CXX bridge to return `Result`, the program calls C++'s by the CXX bridge to return `Result`, the program calls C++'s
`std::terminate`. The behavior is equivalent to the same exception being `std::terminate`. The behavior is equivalent to the same exception being
thrown through a `noexcept` C++ function. thrown through a `noexcept` C++ function.

@ -6,9 +6,9 @@
<details> <details>
* Items declared in the `extern "Rust"` reference items that are in scope in the - Items declared in the `extern "Rust"` reference items that are in scope in the
parent module. parent module.
* The CXX code generator uses your `extern "Rust"` section(s) to produce a C++ - The CXX code generator uses your `extern "Rust"` section(s) to produce a C++
header file containing the corresponding C++ declarations. The generated header file containing the corresponding C++ declarations. The generated
header has the same path as the Rust source file containing the bridge, except header has the same path as the Rust source file containing the bridge, except
with a .rs.h file extension. with a .rs.h file extension.

@ -6,12 +6,12 @@
<details> <details>
* Rust functions that return `Result` are translated to exceptions on the C++ - Rust functions that return `Result` are translated to exceptions on the C++
side. side.
* The exception thrown will always be of type `rust::Error`, which primarily - The exception thrown will always be of type `rust::Error`, which primarily
exposes a way to get the error message string. The error message will come exposes a way to get the error message string. The error message will come
from the error type's `Display` impl. from the error type's `Display` impl.
* A panic unwinding from Rust to C++ will always cause the process to - A panic unwinding from Rust to C++ will always cause the process to
immediately terminate. immediately terminate.
</details> </details>

@ -18,7 +18,7 @@ Generated C++:
<details> <details>
* On the Rust side, the code generated for shared enums is actually a struct - On the Rust side, the code generated for shared enums is actually a struct
wrapping a numeric value. This is because it is not UB in C++ for an enum wrapping a numeric value. This is because it is not UB in C++ for an enum
class to hold a value different from all of the listed variants, and our Rust class to hold a value different from all of the listed variants, and our Rust
representation needs to have the same behavior. representation needs to have the same behavior.

@ -6,8 +6,8 @@
<details> <details>
* Only C-like (unit) enums are supported. - Only C-like (unit) enums are supported.
* A limited number of traits are supported for `#[derive()]` on shared types. - A limited number of traits are supported for `#[derive()]` on shared types.
Corresponding functionality is also generated for the C++ code, e.g. if you Corresponding functionality is also generated for the C++ code, e.g. if you
derive `Hash` also generates an implementation of `std::hash` for the derive `Hash` also generates an implementation of `std::hash` for the
corresponding C++ type. corresponding C++ type.

@ -1,7 +1,7 @@
# Additional Types # Additional Types
| Rust Type | C++ Type | | Rust Type | C++ Type |
|-------------------|----------------------| | ----------------- | -------------------- |
| `String` | `rust::String` | | `String` | `rust::String` |
| `&str` | `rust::Str` | | `&str` | `rust::Str` |
| `CxxString` | `std::string` | | `CxxString` | `std::string` |
@ -13,14 +13,14 @@
<details> <details>
* These types can be used in the fields of shared structs and the arguments and - These types can be used in the fields of shared structs and the arguments and
returns of extern functions. returns of extern functions.
* Note that Rust's `String` does not map directly to `std::string`. There are a - Note that Rust's `String` does not map directly to `std::string`. There are a
few reasons for this: few reasons for this:
* `std::string` does not uphold the UTF-8 invariant that `String` requires. - `std::string` does not uphold the UTF-8 invariant that `String` requires.
* The two types have different layouts in memory and so can't be passed - The two types have different layouts in memory and so can't be passed
directly between languages. directly between languages.
* `std::string` requires move constructors that don't match Rust's move - `std::string` requires move constructors that don't match Rust's move
semantics, so a `std::string` can't be passed by value to Rust. semantics, so a `std::string` can't be passed by value to Rust.
</details> </details>

@ -1,8 +1,9 @@
# Interoperability with Java # Interoperability with Java
Java can load shared objects via [Java Native Interface Java can load shared objects via
(JNI)](https://en.wikipedia.org/wiki/Java_Native_Interface). The [`jni` [Java Native Interface (JNI)](https://en.wikipedia.org/wiki/Java_Native_Interface).
crate](https://docs.rs/jni/) allows you to create a compatible library. The [`jni` crate](https://docs.rs/jni/) allows you to create a compatible
library.
First, we create a Rust function to export to Java: First, we create a Rust function to export to Java:

@ -17,8 +17,8 @@ fn main() {
} }
``` ```
We already saw this in the [Safe FFI Wrapper We already saw this in the
exercise](../../exercises/day-3/safe-ffi-wrapper.md). [Safe FFI Wrapper exercise](../../exercises/day-3/safe-ffi-wrapper.md).
> This assumes full knowledge of the target platform. Not recommended for > This assumes full knowledge of the target platform. Not recommended for
> production. > production.

@ -17,4 +17,3 @@ _interoperability/c/libbirthday/libbirthday.c_:
```c ```c
{{#include c/libbirthday/libbirthday.c}} {{#include c/libbirthday/libbirthday.c}}
``` ```

@ -19,10 +19,7 @@ use birthday_bindgen::{card, print_card};
fn main() { fn main() {
let name = std::ffi::CString::new("Peter").unwrap(); let name = std::ffi::CString::new("Peter").unwrap();
let card = card { let card = card { name: name.as_ptr(), years: 42 };
name: name.as_ptr(),
years: 42,
};
// SAFETY: `print_card` is safe to call with a valid `card` pointer. // SAFETY: `print_card` is safe to call with a valid `card` pointer.
unsafe { unsafe {
print_card(&card as *const card); print_card(&card as *const card);

@ -14,8 +14,8 @@ fn main() {
} }
``` ```
We already saw this in the [Safe FFI Wrapper We already saw this in the
exercise](../../exercises/day-3/safe-ffi-wrapper.md). [Safe FFI Wrapper exercise](../../exercises/day-3/safe-ffi-wrapper.md).
> This assumes full knowledge of the target platform. Not recommended for > This assumes full knowledge of the target platform. Not recommended for
> production. > production.

@ -34,7 +34,6 @@ _interoperability/rust/analyze/Android.bp_
{{#include rust/analyze/Android.bp}} {{#include rust/analyze/Android.bp}}
``` ```
Build, push, and run the binary on your device: Build, push, and run the binary on your device:
```shell ```shell
@ -43,7 +42,8 @@ Build, push, and run the binary on your device:
<details> <details>
`#[no_mangle]` disables Rust's usual name mangling, so the exported symbol will just be the name of `#[no_mangle]` disables Rust's usual name mangling, so the exported symbol will
the function. You can also use `#[export_name = "some_name"]` to specify whatever name you want. just be the name of the function. You can also use
`#[export_name = "some_name"]` to specify whatever name you want.
</details> </details>

@ -1,7 +1,7 @@
# Setup # Setup
We will be using a Cuttlefish Android Virtual Device to test our code. Make sure you We will be using a Cuttlefish Android Virtual Device to test our code. Make sure
have access to one or create a new one with: you have access to one or create a new one with:
```shell ```shell
source build/envsetup.sh source build/envsetup.sh
@ -9,15 +9,18 @@ lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
acloud create acloud create
``` ```
Please see the [Android Developer Please see the
Codelab](https://source.android.com/docs/setup/start) for details. [Android Developer Codelab](https://source.android.com/docs/setup/start) for
details.
<details> <details>
Key points: Key points:
- Cuttlefish is a reference Android device designed to work on generic Linux desktops. MacOS support is also planned. - Cuttlefish is a reference Android device designed to work on generic Linux
desktops. MacOS support is also planned.
- The Cuttlefish system image maintains high fidelity to real devices, and is the ideal emulator to run many Rust use cases. - The Cuttlefish system image maintains high fidelity to real devices, and is
the ideal emulator to run many Rust use cases.
</details> </details>

@ -1,6 +1,7 @@
--- ---
session: Afternoon session: Afternoon
--- ---
# Async Rust # Async Rust
"Async" is a concurrency model where multiple tasks are executed concurrently by "Async" is a concurrency model where multiple tasks are executed concurrently by
@ -11,18 +12,18 @@ very low and operating systems provide primitives for efficiently identifying
I/O that is able to proceed. I/O that is able to proceed.
Rust's asynchronous operation is based on "futures", which represent work that Rust's asynchronous operation is based on "futures", which represent work that
may be completed in the future. Futures are "polled" until they signal that may be completed in the future. Futures are "polled" until they signal that they
they are complete. are complete.
Futures are polled by an async runtime, and several different runtimes are Futures are polled by an async runtime, and several different runtimes are
available. available.
## Comparisons ## Comparisons
* Python has a similar model in its `asyncio`. However, its `Future` type is - Python has a similar model in its `asyncio`. However, its `Future` type is
callback-based, and not polled. Async Python programs require a "loop", callback-based, and not polled. Async Python programs require a "loop",
similar to a runtime in Rust. similar to a runtime in Rust.
* JavaScript's `Promise` is similar, but again callback-based. The language - JavaScript's `Promise` is similar, but again callback-based. The language
runtime implements the event loop, so many of the details of Promise runtime implements the event loop, so many of the details of Promise
resolution are hidden. resolution are hidden.

@ -24,25 +24,25 @@ fn main() {
Key points: Key points:
* Note that this is a simplified example to show the syntax. There is no long - Note that this is a simplified example to show the syntax. There is no long
running operation or any real concurrency in it! running operation or any real concurrency in it!
* What is the return type of an async call? - What is the return type of an async call?
* Use `let future: () = async_main(10);` in `main` to see the type. - Use `let future: () = async_main(10);` in `main` to see the type.
* The "async" keyword is syntactic sugar. The compiler replaces the return type - The "async" keyword is syntactic sugar. The compiler replaces the return type
with a future. with a future.
* You cannot make `main` async, without additional instructions to the compiler - You cannot make `main` async, without additional instructions to the compiler
on how to use the returned future. on how to use the returned future.
* You need an executor to run async code. `block_on` blocks the current thread - You need an executor to run async code. `block_on` blocks the current thread
until the provided future has run to completion. until the provided future has run to completion.
* `.await` asynchronously waits for the completion of another operation. Unlike - `.await` asynchronously waits for the completion of another operation. Unlike
`block_on`, `.await` doesn't block the current thread. `block_on`, `.await` doesn't block the current thread.
* `.await` can only be used inside an `async` function (or block; these are - `.await` can only be used inside an `async` function (or block; these are
introduced later). introduced later).
</details> </details>

@ -32,18 +32,18 @@ async fn main() {
<details> <details>
* Change the channel size to `3` and see how it affects the execution. - Change the channel size to `3` and see how it affects the execution.
* Overall, the interface is similar to the `sync` channels as seen in the - Overall, the interface is similar to the `sync` channels as seen in the
[morning class](concurrency/channels.md). [morning class](concurrency/channels.md).
* Try removing the `std::mem::drop` call. What happens? Why? - Try removing the `std::mem::drop` call. What happens? Why?
* The [Flume](https://docs.rs/flume/latest/flume/) crate has channels that - The [Flume](https://docs.rs/flume/latest/flume/) crate has channels that
implement both `sync` and `async` `send` and `recv`. This can be convenient implement both `sync` and `async` `send` and `recv`. This can be convenient
for complex applications with both IO and heavy CPU processing tasks. for complex applications with both IO and heavy CPU processing tasks.
* What makes working with `async` channels preferable is the ability to combine - What makes working with `async` channels preferable is the ability to combine
them with other `future`s to combine them and create complex control flow. them with other `future`s to combine them and create complex control flow.
</details> </details>

@ -1,8 +1,8 @@
# Join # Join
A join operation waits until all of a set of futures are ready, and A join operation waits until all of a set of futures are ready, and returns a
returns a collection of their results. This is similar to `Promise.all` in collection of their results. This is similar to `Promise.all` in JavaScript or
JavaScript or `asyncio.gather` in Python. `asyncio.gather` in Python.
```rust,editable,compile_fail ```rust,editable,compile_fail
use anyhow::Result; use anyhow::Result;
@ -35,16 +35,17 @@ async fn main() {
Copy this example into your prepared `src/main.rs` and run it from there. Copy this example into your prepared `src/main.rs` and run it from there.
* For multiple futures of disjoint types, you can use `std::future::join!` but - For multiple futures of disjoint types, you can use `std::future::join!` but
you must know how many futures you will have at compile time. This is you must know how many futures you will have at compile time. This is
currently in the `futures` crate, soon to be stabilised in `std::future`. currently in the `futures` crate, soon to be stabilised in `std::future`.
* The risk of `join` is that one of the futures may never resolve, this would - The risk of `join` is that one of the futures may never resolve, this would
cause your program to stall. cause your program to stall.
* You can also combine `join_all` with `join!` for instance to join all requests - You can also combine `join_all` with `join!` for instance to join all requests
to an http service as well as a database query. Try adding a to an http service as well as a database query. Try adding a
`tokio::time::sleep` to the future, using `futures::join!`. This is not a `tokio::time::sleep` to the future, using `futures::join!`. This is not a
timeout (that requires `select!`, explained in the next chapter), but demonstrates `join!`. timeout (that requires `select!`, explained in the next chapter), but
demonstrates `join!`.
</details> </details>

@ -2,8 +2,8 @@
A select operation waits until any of a set of futures is ready, and responds to A select operation waits until any of a set of futures is ready, and responds to
that future's result. In JavaScript, this is similar to `Promise.race`. In that future's result. In JavaScript, this is similar to `Promise.race`. In
Python, it compares to `asyncio.wait(task_set, Python, it compares to
return_when=asyncio.FIRST_COMPLETED)`. `asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)`.
Similar to a match statement, the body of `select!` has a number of arms, each Similar to a match statement, the body of `select!` has a number of arms, each
of the form `pattern = future => statement`. When the `future` is ready, the of the form `pattern = future => statement`. When the `future` is ready, the
@ -36,17 +36,11 @@ async fn main() {
let (dog_sender, dog_receiver) = mpsc::channel(32); let (dog_sender, dog_receiver) = mpsc::channel(32);
tokio::spawn(async move { tokio::spawn(async move {
sleep(Duration::from_millis(500)).await; sleep(Duration::from_millis(500)).await;
cat_sender cat_sender.send(String::from("Felix")).await.expect("Failed to send cat.");
.send(String::from("Felix"))
.await
.expect("Failed to send cat.");
}); });
tokio::spawn(async move { tokio::spawn(async move {
sleep(Duration::from_millis(50)).await; sleep(Duration::from_millis(50)).await;
dog_sender dog_sender.send(String::from("Rex")).await.expect("Failed to send dog.");
.send(String::from("Rex"))
.await
.expect("Failed to send dog.");
}); });
let winner = first_animal_to_finish_race(cat_receiver, dog_receiver) let winner = first_animal_to_finish_race(cat_receiver, dog_receiver)
@ -59,21 +53,21 @@ async fn main() {
<details> <details>
* In this example, we have a race between a cat and a dog. - In this example, we have a race between a cat and a dog.
`first_animal_to_finish_race` listens to both channels and will pick whichever `first_animal_to_finish_race` listens to both channels and will pick whichever
arrives first. Since the dog takes 50ms, it wins against the cat that arrives first. Since the dog takes 50ms, it wins against the cat that take
take 500ms. 500ms.
* You can use `oneshot` channels in this example as the channels are supposed to - You can use `oneshot` channels in this example as the channels are supposed to
receive only one `send`. receive only one `send`.
* Try adding a deadline to the race, demonstrating selecting different sorts of - Try adding a deadline to the race, demonstrating selecting different sorts of
futures. futures.
* Note that `select!` drops unmatched branches, which cancels their futures. - Note that `select!` drops unmatched branches, which cancels their futures. It
It is easiest to use when every execution of `select!` creates new futures. is easiest to use when every execution of `select!` creates new futures.
* An alternative is to pass `&mut future` instead of the future itself, but - An alternative is to pass `&mut future` instead of the future itself, but
this can lead to issues, further discussed in the pinning slide. this can lead to issues, further discussed in the pinning slide.
</details> </details>

@ -1,8 +1,8 @@
# Futures # Futures
[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) [`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) is a trait,
is a trait, implemented by objects that represent an operation that may not be implemented by objects that represent an operation that may not be complete yet.
complete yet. A future can be polled, and `poll` returns a A future can be polled, and `poll` returns a
[`Poll`](https://doc.rust-lang.org/std/task/enum.Poll.html). [`Poll`](https://doc.rust-lang.org/std/task/enum.Poll.html).
```rust ```rust
@ -29,16 +29,16 @@ pause until that Future is ready, and then evaluates to its output.
<details> <details>
* The `Future` and `Poll` types are implemented exactly as shown; click the - The `Future` and `Poll` types are implemented exactly as shown; click the
links to show the implementations in the docs. links to show the implementations in the docs.
* We will not get to `Pin` and `Context`, as we will focus on writing async - We will not get to `Pin` and `Context`, as we will focus on writing async
code, rather than building new async primitives. Briefly: code, rather than building new async primitives. Briefly:
* `Context` allows a Future to schedule itself to be polled again when an - `Context` allows a Future to schedule itself to be polled again when an
event occurs. event occurs.
* `Pin` ensures that the Future isn't moved in memory, so that pointers into - `Pin` ensures that the Future isn't moved in memory, so that pointers into
that future remain valid. This is required to allow references to remain that future remain valid. This is required to allow references to remain
valid after an `.await`. valid after an `.await`.

@ -1,6 +1,8 @@
# Pitfalls of async/await # Pitfalls of async/await
Async / await provides convenient and efficient abstraction for concurrent asynchronous programming. However, the async/await model in Rust also comes with its share of pitfalls and footguns. We illustrate some of them in this chapter: Async / await provides convenient and efficient abstraction for concurrent
asynchronous programming. However, the async/await model in Rust also comes with
its share of pitfalls and footguns. We illustrate some of them in this chapter:
- [Blocking the Executor](pitfalls/blocking-executor.md) - [Blocking the Executor](pitfalls/blocking-executor.md)
- [Pin](pitfalls/pin.md) - [Pin](pitfalls/pin.md)

@ -1,8 +1,10 @@
# Async Traits # Async Traits
Async methods in traits are not yet supported in the stable channel ([An experimental feature exists in nightly and should be stabilized in the mid term.](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html)) Async methods in traits are not yet supported in the stable channel
([An experimental feature exists in nightly and should be stabilized in the mid term.](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html))
The crate [async_trait](https://docs.rs/async-trait/latest/async_trait/) provides a workaround through a macro: The crate [async_trait](https://docs.rs/async-trait/latest/async_trait/)
provides a workaround through a macro:
```rust,editable,compile_fail ```rust,editable,compile_fail
use async_trait::async_trait; use async_trait::async_trait;
@ -25,7 +27,10 @@ impl Sleeper for FixedSleeper {
} }
} }
async fn run_all_sleepers_multiple_times(sleepers: Vec<Box<dyn Sleeper>>, n_times: usize) { async fn run_all_sleepers_multiple_times(
sleepers: Vec<Box<dyn Sleeper>>,
n_times: usize,
) {
for _ in 0..n_times { for _ in 0..n_times {
println!("running all sleepers.."); println!("running all sleepers..");
for sleeper in &sleepers { for sleeper in &sleepers {
@ -48,16 +53,16 @@ async fn main() {
<details> <details>
* `async_trait` is easy to use, but note that it's using heap allocations to - `async_trait` is easy to use, but note that it's using heap allocations to
achieve this. This heap allocation has performance overhead. achieve this. This heap allocation has performance overhead.
* The challenges in language support for `async trait` are deep Rust and - The challenges in language support for `async trait` are deep Rust and
probably not worth describing in-depth. Niko Matsakis did a good job of probably not worth describing in-depth. Niko Matsakis did a good job of
explaining them in [this explaining them in
post](https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/) [this post](https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/)
if you are interested in digging deeper. if you are interested in digging deeper.
* Try creating a new sleeper struct that will sleep for a random amount of time - Try creating a new sleeper struct that will sleep for a random amount of time
and adding it to the Vec. and adding it to the Vec.
</details> </details>

@ -1,8 +1,8 @@
# Blocking the executor # Blocking the executor
Most async runtimes only allow IO tasks to run concurrently. Most async runtimes only allow IO tasks to run concurrently. This means that CPU
This means that CPU blocking tasks will block the executor and prevent other tasks from being executed. blocking tasks will block the executor and prevent other tasks from being
An easy workaround is to use async equivalent methods where possible. executed. An easy workaround is to use async equivalent methods where possible.
```rust,editable,compile_fail ```rust,editable,compile_fail
use futures::future::join_all; use futures::future::join_all;
@ -26,25 +26,25 @@ async fn main() {
<details> <details>
* Run the code and see that the sleeps happen consecutively rather than - Run the code and see that the sleeps happen consecutively rather than
concurrently. concurrently.
* The `"current_thread"` flavor puts all tasks on a single thread. This makes the - The `"current_thread"` flavor puts all tasks on a single thread. This makes
effect more obvious, but the bug is still present in the multi-threaded the effect more obvious, but the bug is still present in the multi-threaded
flavor. flavor.
* Switch the `std::thread::sleep` to `tokio::time::sleep` and await its result. - Switch the `std::thread::sleep` to `tokio::time::sleep` and await its result.
* Another fix would be to `tokio::task::spawn_blocking` which spawns an actual - Another fix would be to `tokio::task::spawn_blocking` which spawns an actual
thread and transforms its handle into a future without blocking the executor. thread and transforms its handle into a future without blocking the executor.
* You should not think of tasks as OS threads. They do not map 1 to 1 and most - You should not think of tasks as OS threads. They do not map 1 to 1 and most
executors will allow many tasks to run on a single OS thread. This is executors will allow many tasks to run on a single OS thread. This is
particularly problematic when interacting with other libraries via FFI, where particularly problematic when interacting with other libraries via FFI, where
that library might depend on thread-local storage or map to specific OS that library might depend on thread-local storage or map to specific OS
threads (e.g., CUDA). Prefer `tokio::task::spawn_blocking` in such situations. threads (e.g., CUDA). Prefer `tokio::task::spawn_blocking` in such situations.
* Use sync mutexes with care. Holding a mutex over an `.await` may cause another - Use sync mutexes with care. Holding a mutex over an `.await` may cause another
task to block, and that task may be running on the same thread. task to block, and that task may be running on the same thread.
</details> </details>

@ -1,9 +1,9 @@
# Cancellation # Cancellation
Dropping a future implies it can never be polled again. This is called *cancellation* Dropping a future implies it can never be polled again. This is called
and it can occur at any `await` point. Care is needed to ensure the system works _cancellation_ and it can occur at any `await` point. Care is needed to ensure
correctly even when futures are cancelled. For example, it shouldn't deadlock or the system works correctly even when futures are cancelled. For example, it
lose data. shouldn't deadlock or lose data.
```rust,editable,compile_fail ```rust,editable,compile_fail
use std::io::{self, ErrorKind}; use std::io::{self, ErrorKind};
@ -29,7 +29,7 @@ impl LinesReader {
} }
} }
if bytes.is_empty() { if bytes.is_empty() {
return Ok(None) return Ok(None);
} }
let s = String::from_utf8(bytes) let s = String::from_utf8(bytes)
.map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?; .map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?;
@ -69,17 +69,19 @@ async fn main() -> std::io::Result<()> {
<details> <details>
* The compiler doesn't help with cancellation-safety. You need to read API - The compiler doesn't help with cancellation-safety. You need to read API
documentation and consider what state your `async fn` holds. documentation and consider what state your `async fn` holds.
* Unlike `panic` and `?`, cancellation is part of normal control flow - Unlike `panic` and `?`, cancellation is part of normal control flow (vs
(vs error-handling). error-handling).
* The example loses parts of the string. - The example loses parts of the string.
* Whenever the `tick()` branch finishes first, `next()` and its `buf` are dropped. - Whenever the `tick()` branch finishes first, `next()` and its `buf` are
dropped.
* `LinesReader` can be made cancellation-safe by making `buf` part of the struct: - `LinesReader` can be made cancellation-safe by making `buf` part of the
struct:
```rust,compile_fail ```rust,compile_fail
struct LinesReader { struct LinesReader {
stream: DuplexStream, stream: DuplexStream,
@ -101,14 +103,15 @@ async fn main() -> std::io::Result<()> {
} }
``` ```
* [`Interval::tick`](https://docs.rs/tokio/latest/tokio/time/struct.Interval.html#method.tick) - [`Interval::tick`](https://docs.rs/tokio/latest/tokio/time/struct.Interval.html#method.tick)
is cancellation-safe because it keeps track of whether a tick has been 'delivered'. is cancellation-safe because it keeps track of whether a tick has been
'delivered'.
* [`AsyncReadExt::read`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncReadExt.html#method.read) - [`AsyncReadExt::read`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncReadExt.html#method.read)
is cancellation-safe because it either returns or doesn't read data. is cancellation-safe because it either returns or doesn't read data.
* [`AsyncBufReadExt::read_line`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line) - [`AsyncBufReadExt::read_line`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line)
is similar to the example and *isn't* cancellation-safe. See its documentation is similar to the example and _isn't_ cancellation-safe. See its documentation
for details and alternatives. for details and alternatives.
</details> </details>

@ -1,9 +1,9 @@
# `Pin` # `Pin`
When you await a future, all local variables (that would ordinarily be stored on When you await a future, all local variables (that would ordinarily be stored on
a stack frame) are instead stored in the Future for the current async block. If your a stack frame) are instead stored in the Future for the current async block. If
future has pointers to data on the stack, those pointers might get invalidated. your future has pointers to data on the stack, those pointers might get
This is unsafe. invalidated. This is unsafe.
Therefore, you must guarantee that the addresses your future points to don't Therefore, you must guarantee that the addresses your future points to don't
change. That is why we need to "pin" futures. Using the same future repeatedly change. That is why we need to "pin" futures. Using the same future repeatedly
@ -43,10 +43,7 @@ async fn worker(mut work_queue: mpsc::Receiver<Work>) {
async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 { async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
work_queue work_queue
.send(Work { .send(Work { input, respond_on: tx })
input,
respond_on: tx,
})
.await .await
.expect("failed to send on work queue"); .expect("failed to send on work queue");
rx.await.expect("failed waiting for response") rx.await.expect("failed waiting for response")
@ -65,16 +62,16 @@ async fn main() {
<details> <details>
* You may recognize this as an example of the actor pattern. Actors - You may recognize this as an example of the actor pattern. Actors typically
typically call `select!` in a loop. call `select!` in a loop.
* This serves as a summation of a few of the previous lessons, so take your time - This serves as a summation of a few of the previous lessons, so take your time
with it. with it.
* Naively add a `_ = sleep(Duration::from_millis(100)) => { println!(..) }` - Naively add a `_ = sleep(Duration::from_millis(100)) => { println!(..) }` to
to the `select!`. This will never execute. Why? the `select!`. This will never execute. Why?
* Instead, add a `timeout_fut` containing that future outside of the `loop`: - Instead, add a `timeout_fut` containing that future outside of the `loop`:
```rust,compile_fail ```rust,compile_fail
let mut timeout_fut = sleep(Duration::from_millis(100)); let mut timeout_fut = sleep(Duration::from_millis(100));
@ -85,7 +82,7 @@ async fn main() {
} }
} }
``` ```
* This still doesn't work. Follow the compiler errors, adding `&mut` to the - This still doesn't work. Follow the compiler errors, adding `&mut` to the
`timeout_fut` in the `select!` to work around the move, then using `timeout_fut` in the `select!` to work around the move, then using
`Box::pin`: `Box::pin`:
@ -99,14 +96,15 @@ async fn main() {
} }
``` ```
* This compiles, but once the timeout expires it is `Poll::Ready` on every - This compiles, but once the timeout expires it is `Poll::Ready` on every
iteration (a fused future would help with this). Update to reset iteration (a fused future would help with this). Update to reset
`timeout_fut` every time it expires. `timeout_fut` every time it expires.
* Box allocates on the heap. In some cases, `std::pin::pin!` (only recently - Box allocates on the heap. In some cases, `std::pin::pin!` (only recently
stabilized, with older code often using `tokio::pin!`) is also an option, but stabilized, with older code often using `tokio::pin!`) is also an option, but
that is difficult to use for a future that is reassigned. that is difficult to use for a future that is reassigned.
* Another alternative is to not use `pin` at all but spawn another task that will send to a `oneshot` channel every 100ms. - Another alternative is to not use `pin` at all but spawn another task that
will send to a `oneshot` channel every 100ms.
</details> </details>

@ -1,15 +1,15 @@
# Runtimes # Runtimes
A *runtime* provides support for performing operations asynchronously (a A _runtime_ provides support for performing operations asynchronously (a
*reactor*) and is responsible for executing futures (an *executor*). Rust does not have a _reactor_) and is responsible for executing futures (an _executor_). Rust does
"built-in" runtime, but several options are available: not have a "built-in" runtime, but several options are available:
* [Tokio](https://tokio.rs/): performant, with a well-developed ecosystem of - [Tokio](https://tokio.rs/): performant, with a well-developed ecosystem of
functionality like [Hyper](https://hyper.rs/) for HTTP or functionality like [Hyper](https://hyper.rs/) for HTTP or
[Tonic](https://github.com/hyperium/tonic) for gRPC. [Tonic](https://github.com/hyperium/tonic) for gRPC.
* [async-std](https://async.rs/): aims to be a "std for async", and includes a - [async-std](https://async.rs/): aims to be a "std for async", and includes a
basic runtime in `async::task`. basic runtime in `async::task`.
* [smol](https://docs.rs/smol/latest/smol/): simple and lightweight - [smol](https://docs.rs/smol/latest/smol/): simple and lightweight
Several larger applications have their own runtimes. For example, Several larger applications have their own runtimes. For example,
[Fuchsia](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/lib/fuchsia-async/src/lib.rs) [Fuchsia](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/lib/fuchsia-async/src/lib.rs)
@ -17,11 +17,11 @@ already has one.
<details> <details>
* Note that of the listed runtimes, only Tokio is supported in the Rust - Note that of the listed runtimes, only Tokio is supported in the Rust
playground. The playground also does not permit any I/O, so most interesting playground. The playground also does not permit any I/O, so most interesting
async things can't run in the playground. async things can't run in the playground.
* Futures are "inert" in that they do not do anything (not even start an I/O - Futures are "inert" in that they do not do anything (not even start an I/O
operation) unless there is an executor polling them. This differs from JS operation) unless there is an executor polling them. This differs from JS
Promises, for example, which will run to completion even if they are never Promises, for example, which will run to completion even if they are never
used. used.

@ -1,11 +1,10 @@
# Tokio # Tokio
Tokio provides: Tokio provides:
* A multi-threaded runtime for executing asynchronous code. - A multi-threaded runtime for executing asynchronous code.
* An asynchronous version of the standard library. - An asynchronous version of the standard library.
* A large ecosystem of libraries. - A large ecosystem of libraries.
```rust,editable,compile_fail ```rust,editable,compile_fail
use tokio::time; use tokio::time;
@ -30,20 +29,20 @@ async fn main() {
<details> <details>
* With the `tokio::main` macro we can now make `main` async. - With the `tokio::main` macro we can now make `main` async.
* The `spawn` function creates a new, concurrent "task". - The `spawn` function creates a new, concurrent "task".
* Note: `spawn` takes a `Future`, you don't call `.await` on `count_to`. - Note: `spawn` takes a `Future`, you don't call `.await` on `count_to`.
**Further exploration:** **Further exploration:**
* Why does `count_to` not (usually) get to 10? This is an example of async - Why does `count_to` not (usually) get to 10? This is an example of async
cancellation. `tokio::spawn` returns a handle which can be awaited to wait cancellation. `tokio::spawn` returns a handle which can be awaited to wait
until it finishes. until it finishes.
* Try `count_to(10).await` instead of spawning. - Try `count_to(10).await` instead of spawning.
* Try awaiting the task returned from `tokio::spawn`. - Try awaiting the task returned from `tokio::spawn`.
</details> </details>

@ -51,15 +51,18 @@ async fn main() -> io::Result<()> {
Copy this example into your prepared `src/main.rs` and run it from there. Copy this example into your prepared `src/main.rs` and run it from there.
Try connecting to it with a TCP connection tool like [nc](https://www.unix.com/man-page/linux/1/nc/) or [telnet](https://www.unix.com/man-page/linux/1/telnet/). Try connecting to it with a TCP connection tool like
[nc](https://www.unix.com/man-page/linux/1/nc/) or
[telnet](https://www.unix.com/man-page/linux/1/telnet/).
* Ask students to visualize what the state of the example server would be with a - Ask students to visualize what the state of the example server would be with a
few connected clients. What tasks exist? What are their Futures? few connected clients. What tasks exist? What are their Futures?
* This is the first time we've seen an `async` block. This is similar to a - This is the first time we've seen an `async` block. This is similar to a
closure, but does not take any arguments. Its return value is a Future, closure, but does not take any arguments. Its return value is a Future,
similar to an `async fn`. similar to an `async fn`.
* Refactor the async block into a function, and improve the error handling using `?`. - Refactor the async block into a function, and improve the error handling using
`?`.
</details> </details>

@ -2,27 +2,32 @@
course: Bare Metal course: Bare Metal
session: Morning session: Morning
--- ---
# Welcome to Bare Metal Rust # Welcome to Bare Metal Rust
This is a standalone one-day course about bare-metal Rust, aimed at people who are familiar with the This is a standalone one-day course about bare-metal Rust, aimed at people who
basics of Rust (perhaps from completing the Comprehensive Rust course), and ideally also have some are familiar with the basics of Rust (perhaps from completing the Comprehensive
experience with bare-metal programming in some other language such as C. Rust course), and ideally also have some experience with bare-metal programming
in some other language such as C.
Today we will talk about 'bare-metal' Rust: running Rust code without an OS underneath us. This will Today we will talk about 'bare-metal' Rust: running Rust code without an OS
be divided into several parts: underneath us. This will be divided into several parts:
- What is `no_std` Rust? - What is `no_std` Rust?
- Writing firmware for microcontrollers. - Writing firmware for microcontrollers.
- Writing bootloader / kernel code for application processors. - Writing bootloader / kernel code for application processors.
- Some useful crates for bare-metal Rust development. - Some useful crates for bare-metal Rust development.
For the microcontroller part of the course we will use the [BBC micro:bit](https://microbit.org/) v2 For the microcontroller part of the course we will use the
as an example. It's a [development board](https://tech.microbit.org/hardware/) based on the Nordic [BBC micro:bit](https://microbit.org/) v2 as an example. It's a
nRF51822 microcontroller with some LEDs and buttons, an I2C-connected accelerometer and compass, and [development board](https://tech.microbit.org/hardware/) based on the Nordic
an on-board SWD debugger. nRF51822 microcontroller with some LEDs and buttons, an I2C-connected
accelerometer and compass, and an on-board SWD debugger.
To get started, install some tools we'll need later. On gLinux or Debian: To get started, install some tools we'll need later. On gLinux or Debian:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```bash ```bash
sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm
rustup update rustup update
@ -32,7 +37,9 @@ cargo install cargo-binutils cargo-embed
``` ```
And give users in the `plugdev` group access to the micro:bit programmer: And give users in the `plugdev` group access to the micro:bit programmer:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```bash ```bash
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", MODE="0664", GROUP="plugdev"' |\ echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", MODE="0664", GROUP="plugdev"' |\
sudo tee /etc/udev/rules.d/50-microbit.rules sudo tee /etc/udev/rules.d/50-microbit.rules
@ -40,7 +47,9 @@ sudo udevadm control --reload-rules
``` ```
On MacOS: On MacOS:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```bash ```bash
xcode-select --install xcode-select --install
brew install gdb picocom qemu brew install gdb picocom qemu

@ -32,9 +32,7 @@ pub fn entry() {
// Safe because `HEAP` is only used here and `entry` is only called once. // Safe because `HEAP` is only used here and `entry` is only called once.
unsafe { unsafe {
// Give the allocator some memory to allocate. // Give the allocator some memory to allocate.
HEAP_ALLOCATOR HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len());
.lock()
.init(HEAP.as_mut_ptr() as usize, HEAP.len());
} }
// Now we can do things that require heap allocation. // Now we can do things that require heap allocation.

@ -9,14 +9,16 @@ To use `alloc` you must implement a
<details> <details>
* `buddy_system_allocator` is a third-party crate implementing a basic buddy system allocator. Other - `buddy_system_allocator` is a third-party crate implementing a basic buddy
crates are available, or you can write your own or hook into your existing allocator. system allocator. Other crates are available, or you can write your own or
* The const parameter of `LockedHeap` is the max order of the allocator; i.e. in this case it can hook into your existing allocator.
allocate regions of up to 2**32 bytes. - The const parameter of `LockedHeap` is the max order of the allocator; i.e. in
* If any crate in your dependency tree depends on `alloc` then you must have exactly one global this case it can allocate regions of up to 2**32 bytes.
allocator defined in your binary. Usually this is done in the top-level binary crate. - If any crate in your dependency tree depends on `alloc` then you must have
* `extern crate panic_halt as _` is necessary to ensure that the `panic_halt` crate is linked in so exactly one global allocator defined in your binary. Usually this is done in
we get its panic handler. the top-level binary crate.
* This example will build but not run, as it doesn't have an entry point. - `extern crate panic_halt as _` is necessary to ensure that the `panic_halt`
crate is linked in so we get its panic handler.
- This example will build but not run, as it doesn't have an entry point.
</details> </details>

@ -1,9 +1,12 @@
# Android # Android
To build a bare-metal Rust binary in AOSP, you need to use a `rust_ffi_static` Soong rule to build To build a bare-metal Rust binary in AOSP, you need to use a `rust_ffi_static`
your Rust code, then a `cc_binary` with a linker script to produce the binary itself, and then a Soong rule to build your Rust code, then a `cc_binary` with a linker script to
`raw_binary` to convert the ELF to a raw binary ready to be run. produce the binary itself, and then a `raw_binary` to convert the ELF to a raw
binary ready to be run.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```soong ```soong
rust_ffi_static { rust_ffi_static {
name: "libvmbase_example", name: "libvmbase_example",

@ -1,8 +1,11 @@
# vmbase # vmbase
For VMs running under crosvm on aarch64, the [vmbase][1] library provides a linker script and useful For VMs running under crosvm on aarch64, the [vmbase][1] library provides a
defaults for the build rules, along with an entry point, UART console logging and more. linker script and useful defaults for the build rules, along with an entry
point, UART console logging and more.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,compile_fail ```rust,compile_fail
#![no_main] #![no_main]
#![no_std] #![no_std]
@ -18,9 +21,10 @@ pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
<details> <details>
* The `main!` macro marks your main function, to be called from the `vmbase` entry point. - The `main!` macro marks your main function, to be called from the `vmbase`
* The `vmbase` entry point handles console initialisation, and issues a PSCI_SYSTEM_OFF to shutdown entry point.
the VM if your main function returns. - The `vmbase` entry point handles console initialisation, and issues a
PSCI_SYSTEM_OFF to shutdown the VM if your main function returns.
</details> </details>

@ -1,18 +1,21 @@
--- ---
session: Afternoon session: Afternoon
--- ---
# Application processors # Application processors
So far we've talked about microcontrollers, such as the Arm Cortex-M series. Now let's try writing So far we've talked about microcontrollers, such as the Arm Cortex-M series. Now
something for Cortex-A. For simplicity we'll just work with QEMU's aarch64 let's try writing something for Cortex-A. For simplicity we'll just work with
QEMU's aarch64
['virt'](https://qemu-project.gitlab.io/qemu/system/arm/virt.html) board. ['virt'](https://qemu-project.gitlab.io/qemu/system/arm/virt.html) board.
<details> <details>
* Broadly speaking, microcontrollers don't have an MMU or multiple levels of privilege (exception - Broadly speaking, microcontrollers don't have an MMU or multiple levels of
levels on Arm CPUs, rings on x86), while application processors do. privilege (exception levels on Arm CPUs, rings on x86), while application
* QEMU supports emulating various different machines or board models for each architecture. The processors do.
'virt' board doesn't correspond to any particular real hardware, but is designed purely for - QEMU supports emulating various different machines or board models for each
virtual machines. architecture. The 'virt' board doesn't correspond to any particular real
hardware, but is designed purely for virtual machines.
</details> </details>

@ -1,8 +1,8 @@
# A better UART driver # A better UART driver
The PL011 actually has [a bunch more registers][1], and adding offsets to construct pointers to access The PL011 actually has [a bunch more registers][1], and adding offsets to
them is error-prone and hard to read. Plus, some of them are bit fields which would be nice to construct pointers to access them is error-prone and hard to read. Plus, some of
access in a structured way. them are bit fields which would be nice to access in a structured way.
| Offset | Register name | Width | | Offset | Register name | Width |
| ------ | ------------- | ----- | | ------ | ------------- | ----- |

@ -1,6 +1,7 @@
# Bitflags # Bitflags
The [`bitflags`](https://crates.io/crates/bitflags) crate is useful for working with bitflags. The [`bitflags`](https://crates.io/crates/bitflags) crate is useful for working
with bitflags.
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include ../examples/src/pl011.rs:Flags}} {{#include ../examples/src/pl011.rs:Flags}}
@ -8,7 +9,7 @@ The [`bitflags`](https://crates.io/crates/bitflags) crate is useful for working
<details> <details>
* The `bitflags!` macro creates a newtype something like `Flags(u16)`, along with a bunch of method - The `bitflags!` macro creates a newtype something like `Flags(u16)`, along
implementations to get and set flags. with a bunch of method implementations to get and set flags.
</details> </details>

@ -8,7 +8,7 @@ Now let's use the new `Registers` struct in our driver.
<details> <details>
* Note the use of `addr_of!` / `addr_of_mut!` to get pointers to individual fields without creating - Note the use of `addr_of!` / `addr_of_mut!` to get pointers to individual
an intermediate reference, which would be unsound. fields without creating an intermediate reference, which would be unsound.
</details> </details>

@ -1,16 +1,19 @@
# Multiple registers # Multiple registers
We can use a struct to represent the memory layout of the UART's registers. We can use a struct to represent the memory layout of the UART's registers.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include ../examples/src/pl011.rs:Registers}} {{#include ../examples/src/pl011.rs:Registers}}
``` ```
<details> <details>
* [`#[repr(C)]`](https://doc.rust-lang.org/reference/type-layout.html#the-c-representation) tells - [`#[repr(C)]`](https://doc.rust-lang.org/reference/type-layout.html#the-c-representation)
the compiler to lay the struct fields out in order, following the same rules as C. This is tells the compiler to lay the struct fields out in order, following the same
necessary for our struct to have a predictable layout, as default Rust representation allows the rules as C. This is necessary for our struct to have a predictable layout, as
compiler to (among other things) reorder fields however it sees fit. default Rust representation allows the compiler to (among other things)
reorder fields however it sees fit.
</details> </details>

@ -1,7 +1,7 @@
# Using it # Using it
Let's write a small program using our driver to write to the serial console, and echo incoming Let's write a small program using our driver to write to the serial console, and
bytes. echo incoming bytes.
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include ../examples/src/main_improved.rs:main}} {{#include ../examples/src/main_improved.rs:main}}
@ -9,8 +9,9 @@ bytes.
<details> <details>
* As in the [inline assembly](../inline-assembly.md) example, this `main` function is called from our - As in the [inline assembly](../inline-assembly.md) example, this `main`
entry point code in `entry.S`. See the speaker notes there for details. function is called from our entry point code in `entry.S`. See the speaker
* Run the example in QEMU with `make qemu` under `src/bare-metal/aps/examples`. notes there for details.
- Run the example in QEMU with `make qemu` under `src/bare-metal/aps/examples`.
</details> </details>

@ -8,28 +8,36 @@ Before we can start running Rust code, we need to do some initialisation.
<details> <details>
* This is the same as it would be for C: initialising the processor state, zeroing the BSS, and - This is the same as it would be for C: initialising the processor state,
setting up the stack pointer. zeroing the BSS, and setting up the stack pointer.
* The BSS (block starting symbol, for historical reasons) is the part of the object file which - The BSS (block starting symbol, for historical reasons) is the part of the
containing statically allocated variables which are initialised to zero. They are omitted from object file which containing statically allocated variables which are
the image, to avoid wasting space on zeroes. The compiler assumes that the loader will take care initialised to zero. They are omitted from the image, to avoid wasting space
of zeroing them. on zeroes. The compiler assumes that the loader will take care of zeroing
* The BSS may already be zeroed, depending on how memory is initialised and the image is loaded, but them.
we zero it to be sure. - The BSS may already be zeroed, depending on how memory is initialised and the
* We need to enable the MMU and cache before reading or writing any memory. If we don't: image is loaded, but we zero it to be sure.
* Unaligned accesses will fault. We build the Rust code for the `aarch64-unknown-none` target - We need to enable the MMU and cache before reading or writing any memory. If
which sets `+strict-align` to prevent the compiler generating unaligned accesses, so it should we don't:
be fine in this case, but this is not necessarily the case in general. - Unaligned accesses will fault. We build the Rust code for the
* If it were running in a VM, this can lead to cache coherency issues. The problem is that the VM `aarch64-unknown-none` target which sets `+strict-align` to prevent the
is accessing memory directly with the cache disabled, while the host has cacheable aliases to the compiler generating unaligned accesses, so it should be fine in this case,
same memory. Even if the host doesn't explicitly access the memory, speculative accesses can but this is not necessarily the case in general.
lead to cache fills, and then changes from one or the other will get lost when the cache is - If it were running in a VM, this can lead to cache coherency issues. The
cleaned or the VM enables the cache. (Cache is keyed by physical address, not VA or IPA.) problem is that the VM is accessing memory directly with the cache disabled,
* For simplicity, we just use a hardcoded pagetable (see `idmap.S`) which identity maps the first 1 while the host has cacheable aliases to the same memory. Even if the host
GiB of address space for devices, the next 1 GiB for DRAM, and another 1 GiB higher up for more doesn't explicitly access the memory, speculative accesses can lead to cache
devices. This matches the memory layout that QEMU uses. fills, and then changes from one or the other will get lost when the cache
* We also set up the exception vector (`vbar_el1`), which we'll see more about later. is cleaned or the VM enables the cache. (Cache is keyed by physical address,
* All examples this afternoon assume we will be running at exception level 1 (EL1). If you need to not VA or IPA.)
run at a different exception level you'll need to modify `entry.S` accordingly. - For simplicity, we just use a hardcoded pagetable (see `idmap.S`) which
identity maps the first 1 GiB of address space for devices, the next 1 GiB for
DRAM, and another 1 GiB higher up for more devices. This matches the memory
layout that QEMU uses.
- We also set up the exception vector (`vbar_el1`), which we'll see more about
later.
- All examples this afternoon assume we will be running at exception level 1
(EL1). If you need to run at a different exception level you'll need to modify
`entry.S` accordingly.
</details> </details>

@ -18,9 +18,7 @@ use core::fmt::Write;
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
use spin::mutex::SpinMutex; use spin::mutex::SpinMutex;
static LOGGER: Logger = Logger { static LOGGER: Logger = Logger { uart: SpinMutex::new(None) };
uart: SpinMutex::new(None),
};
struct Logger { struct Logger {
uart: SpinMutex<Option<Uart>>, uart: SpinMutex<Option<Uart>>,

@ -112,9 +112,7 @@ impl Uart {
/// PL011 device, which must be mapped into the address space of the process /// PL011 device, which must be mapped into the address space of the process
/// as device memory and not have any other aliases. /// as device memory and not have any other aliases.
pub unsafe fn new(base_address: *mut u32) -> Self { pub unsafe fn new(base_address: *mut u32) -> Self {
Self { Self { registers: base_address as *mut Registers }
registers: base_address as *mut Registers,
}
} }
/// Writes a single byte to the UART. /// Writes a single byte to the UART.
@ -133,7 +131,8 @@ impl Uart {
while self.read_flag_register().contains(Flags::BUSY) {} while self.read_flag_register().contains(Flags::BUSY) {}
} }
/// Reads and returns a pending byte, or `None` if nothing has been received. /// Reads and returns a pending byte, or `None` if nothing has been
/// received.
pub fn read_byte(&self) -> Option<u8> { pub fn read_byte(&self) -> Option<u8> {
if self.read_flag_register().contains(Flags::RXFE) { if self.read_flag_register().contains(Flags::RXFE) {
None None

@ -1,26 +1,30 @@
# Exceptions # Exceptions
AArch64 defines an exception vector table with 16 entries, for 4 types of exceptions (synchronous, AArch64 defines an exception vector table with 16 entries, for 4 types of
IRQ, FIQ, SError) from 4 states (current EL with SP0, current EL with SPx, lower EL using AArch64, exceptions (synchronous, IRQ, FIQ, SError) from 4 states (current EL with SP0,
lower EL using AArch32). We implement this in assembly to save volatile registers to the stack current EL with SPx, lower EL using AArch64, lower EL using AArch32). We
before calling into Rust code: implement this in assembly to save volatile registers to the stack before
calling into Rust code:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/exceptions.rs:exceptions}} {{#include examples/src/exceptions.rs:exceptions}}
``` ```
<details> <details>
* EL is exception level; all our examples this afternoon run in EL1. - EL is exception level; all our examples this afternoon run in EL1.
* For simplicity we aren't distinguishing between SP0 and SPx for the current EL exceptions, or - For simplicity we aren't distinguishing between SP0 and SPx for the current EL
between AArch32 and AArch64 for the lower EL exceptions. exceptions, or between AArch32 and AArch64 for the lower EL exceptions.
* For this example we just log the exception and power down, as we don't expect any of them to - For this example we just log the exception and power down, as we don't expect
actually happen. any of them to actually happen.
* We can think of exception handlers and our main execution context more or less like different - We can think of exception handlers and our main execution context more or less
threads. [`Send` and `Sync`][1] will control what we can share between them, just like with threads. like different threads. [`Send` and `Sync`][1] will control what we can share
For example, if we want to share some value between exception handlers and the rest of the between them, just like with threads. For example, if we want to share some
program, and it's `Send` but not `Sync`, then we'll need to wrap it in something like a `Mutex` value between exception handlers and the rest of the program, and it's `Send`
and put it in a static. but not `Sync`, then we'll need to wrap it in something like a `Mutex` and put
it in a static.
</details> </details>

@ -1,30 +1,35 @@
# Inline assembly # Inline assembly
Sometimes we need to use assembly to do things that aren't possible with Rust code. For example, Sometimes we need to use assembly to do things that aren't possible with Rust
to make an HVC (hypervisor call) to tell the firmware to power off the system: code. For example, to make an HVC (hypervisor call) to tell the firmware to
power off the system:
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/main_psci.rs:main}} {{#include examples/src/main_psci.rs:main}}
``` ```
(If you actually want to do this, use the [`smccc`][1] crate which has wrappers for all these functions.) (If you actually want to do this, use the [`smccc`][1] crate which has wrappers
for all these functions.)
<details> <details>
* PSCI is the Arm Power State Coordination Interface, a standard set of functions to manage system - PSCI is the Arm Power State Coordination Interface, a standard set of
and CPU power states, among other things. It is implemented by EL3 firmware and hypervisors on functions to manage system and CPU power states, among other things. It is
many systems. implemented by EL3 firmware and hypervisors on many systems.
* The `0 => _` syntax means initialise the register to 0 before running the inline assembly code, - The `0 => _` syntax means initialise the register to 0 before running the
and ignore its contents afterwards. We need to use `inout` rather than `in` because the call could inline assembly code, and ignore its contents afterwards. We need to use
potentially clobber the contents of the registers. `inout` rather than `in` because the call could potentially clobber the
* This `main` function needs to be `#[no_mangle]` and `extern "C"` because it is called from our contents of the registers.
entry point in `entry.S`. - This `main` function needs to be `#[no_mangle]` and `extern "C"` because it is
* `_x0``_x3` are the values of registers `x0``x3`, which are conventionally used by the bootloader called from our entry point in `entry.S`.
to pass things like a pointer to the device tree. According to the standard aarch64 calling - `_x0``_x3` are the values of registers `x0``x3`, which are conventionally
convention (which is what `extern "C"` specifies to use), registers `x0``x7` are used for the used by the bootloader to pass things like a pointer to the device tree.
first 8 arguments passed to a function, so `entry.S` doesn't need to do anything special except According to the standard aarch64 calling convention (which is what
make sure it doesn't change these registers. `extern "C"` specifies to use), registers `x0``x7` are used for the first 8
* Run the example in QEMU with `make qemu_psci` under `src/bare-metal/aps/examples`. arguments passed to a function, so `entry.S` doesn't need to do anything
special except make sure it doesn't change these registers.
- Run the example in QEMU with `make qemu_psci` under
`src/bare-metal/aps/examples`.
</details> </details>

@ -1,7 +1,7 @@
# Logging # Logging
It would be nice to be able to use the logging macros from the [`log`][1] crate. We can do this by It would be nice to be able to use the logging macros from the [`log`][1] crate.
implementing the `Log` trait. We can do this by implementing the `Log` trait.
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/logger.rs:main}} {{#include examples/src/logger.rs:main}}
@ -9,7 +9,8 @@ implementing the `Log` trait.
<details> <details>
* The unwrap in `log` is safe because we initialise `LOGGER` before calling `set_logger`. - The unwrap in `log` is safe because we initialise `LOGGER` before calling
`set_logger`.
</details> </details>

@ -8,7 +8,8 @@ We need to initialise the logger before we use it.
<details> <details>
* Note that our panic handler can now log details of panics. - Note that our panic handler can now log details of panics.
* Run the example in QEMU with `make qemu_logger` under `src/bare-metal/aps/examples`. - Run the example in QEMU with `make qemu_logger` under
`src/bare-metal/aps/examples`.
</details> </details>

@ -1,17 +1,21 @@
# Volatile memory access for MMIO # Volatile memory access for MMIO
* Use `pointer::read_volatile` and `pointer::write_volatile`. - Use `pointer::read_volatile` and `pointer::write_volatile`.
* Never hold a reference. - Never hold a reference.
* `addr_of!` lets you get fields of structs without creating an intermediate reference. - `addr_of!` lets you get fields of structs without creating an intermediate
reference.
<details> <details>
* Volatile access: read or write operations may have side-effects, so prevent the compiler or - Volatile access: read or write operations may have side-effects, so prevent
hardware from reordering, duplicating or eliding them. the compiler or hardware from reordering, duplicating or eliding them.
* Usually if you write and then read, e.g. via a mutable reference, the compiler may assume that - Usually if you write and then read, e.g. via a mutable reference, the
the value read is the same as the value just written, and not bother actually reading memory. compiler may assume that the value read is the same as the value just
* Some existing crates for volatile access to hardware do hold references, but this is unsound. written, and not bother actually reading memory.
Whenever a reference exist, the compiler may choose to dereference it. - Some existing crates for volatile access to hardware do hold references, but
* Use the `addr_of!` macro to get struct field pointers from a pointer to the struct. this is unsound. Whenever a reference exist, the compiler may choose to
dereference it.
- Use the `addr_of!` macro to get struct field pointers from a pointer to the
struct.
</details> </details>

@ -1,29 +1,31 @@
# Other projects # Other projects
* [oreboot](https://github.com/oreboot/oreboot) - [oreboot](https://github.com/oreboot/oreboot)
* "coreboot without the C" - "coreboot without the C"
* Supports x86, aarch64 and RISC-V. - Supports x86, aarch64 and RISC-V.
* Relies on LinuxBoot rather than having many drivers itself. - Relies on LinuxBoot rather than having many drivers itself.
* [Rust RaspberryPi OS tutorial](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials) - [Rust RaspberryPi OS tutorial](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials)
* Initialisation, UART driver, simple bootloader, JTAG, exception levels, exception handling, - Initialisation, UART driver, simple bootloader, JTAG, exception levels,
page tables exception handling, page tables
* Some dodginess around cache maintenance and initialisation in Rust, not necessarily a good - Some dodginess around cache maintenance and initialisation in Rust, not
example to copy for production code. necessarily a good example to copy for production code.
* [`cargo-call-stack`](https://crates.io/crates/cargo-call-stack) - [`cargo-call-stack`](https://crates.io/crates/cargo-call-stack)
* Static analysis to determine maximum stack usage. - Static analysis to determine maximum stack usage.
<details> <details>
* The RaspberryPi OS tutorial runs Rust code before the MMU and caches are enabled. This will read - The RaspberryPi OS tutorial runs Rust code before the MMU and caches are
and write memory (e.g. the stack). However: enabled. This will read and write memory (e.g. the stack). However:
* Without the MMU and cache, unaligned accesses will fault. It builds with `aarch64-unknown-none` - Without the MMU and cache, unaligned accesses will fault. It builds with
which sets `+strict-align` to prevent the compiler generating unaligned accesses so it should be `aarch64-unknown-none` which sets `+strict-align` to prevent the compiler
alright, but this is not necessarily the case in general. generating unaligned accesses so it should be alright, but this is not
* If it were running in a VM, this can lead to cache coherency issues. The problem is that the VM necessarily the case in general.
is accessing memory directly with the cache disabled, while the host has cacheable aliases to the - If it were running in a VM, this can lead to cache coherency issues. The
same memory. Even if the host doesn't explicitly access the memory, speculative accesses can problem is that the VM is accessing memory directly with the cache disabled,
lead to cache fills, and then changes from one or the other will get lost. Again this is alright while the host has cacheable aliases to the same memory. Even if the host
in this particular case (running directly on the hardware with no hypervisor), but isn't a good doesn't explicitly access the memory, speculative accesses can lead to cache
pattern in general. fills, and then changes from one or the other will get lost. Again this is
alright in this particular case (running directly on the hardware with no
hypervisor), but isn't a good pattern in general.
</details> </details>

@ -8,16 +8,18 @@ The QEMU 'virt' machine has a [PL011][1] UART, so let's write a driver for that.
<details> <details>
* Note that `Uart::new` is unsafe while the other methods are safe. This is because as long as the - Note that `Uart::new` is unsafe while the other methods are safe. This is
caller of `Uart::new` guarantees that its safety requirements are met (i.e. that there is only because as long as the caller of `Uart::new` guarantees that its safety
ever one instance of the driver for a given UART, and nothing else aliasing its address space), requirements are met (i.e. that there is only ever one instance of the driver
then it is always safe to call `write_byte` later because we can assume the necessary for a given UART, and nothing else aliasing its address space), then it is
always safe to call `write_byte` later because we can assume the necessary
preconditions. preconditions.
* We could have done it the other way around (making `new` safe but `write_byte` unsafe), but that - We could have done it the other way around (making `new` safe but `write_byte`
would be much less convenient to use as every place that calls `write_byte` would need to reason unsafe), but that would be much less convenient to use as every place that
about the safety calls `write_byte` would need to reason about the safety
* This is a common pattern for writing safe wrappers of unsafe code: moving the burden of proof for - This is a common pattern for writing safe wrappers of unsafe code: moving the
soundness from a large number of places to a smaller number of places. burden of proof for soundness from a large number of places to a smaller
number of places.
</details> </details>

@ -1,6 +1,7 @@
# More traits # More traits
We derived the `Debug` trait. It would be useful to implement a few more traits too. We derived the `Debug` trait. It would be useful to implement a few more traits
too.
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include ../examples/src/pl011_minimal.rs:Traits}} {{#include ../examples/src/pl011_minimal.rs:Traits}}
@ -8,7 +9,9 @@ We derived the `Debug` trait. It would be useful to implement a few more traits
<details> <details>
* Implementing `Write` lets us use the `write!` and `writeln!` macros with our `Uart` type. - Implementing `Write` lets us use the `write!` and `writeln!` macros with our
* Run the example in QEMU with `make qemu_minimal` under `src/bare-metal/aps/examples`. `Uart` type.
- Run the example in QEMU with `make qemu_minimal` under
`src/bare-metal/aps/examples`.
</details> </details>

@ -1,17 +1,21 @@
# Microcontrollers # Microcontrollers
The `cortex_m_rt` crate provides (among other things) a reset handler for Cortex M microcontrollers. The `cortex_m_rt` crate provides (among other things) a reset handler for Cortex
M microcontrollers.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include microcontrollers/examples/src/bin/minimal.rs:Example}} {{#include microcontrollers/examples/src/bin/minimal.rs:Example}}
``` ```
Next we'll look at how to access peripherals, with increasing levels of abstraction. Next we'll look at how to access peripherals, with increasing levels of
abstraction.
<details> <details>
* The `cortex_m_rt::entry` macro requires that the function have type `fn() -> !`, because returning - The `cortex_m_rt::entry` macro requires that the function have type
to the reset handler doesn't make sense. `fn() -> !`, because returning to the reset handler doesn't make sense.
* Run the example with `cargo embed --bin minimal` - Run the example with `cargo embed --bin minimal`
</details> </details>

@ -1,18 +1,21 @@
# Board support crates # Board support crates
Board support crates provide a further level of wrapping for a specific board for convenience. Board support crates provide a further level of wrapping for a specific board
for convenience.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/bin/board_support.rs:Example}} {{#include examples/src/bin/board_support.rs:Example}}
``` ```
<details> <details>
* In this case the board support crate is just providing more useful names, and a bit of - In this case the board support crate is just providing more useful names, and
initialisation. a bit of initialisation.
* The crate may also include drivers for some on-board devices outside of the microcontroller - The crate may also include drivers for some on-board devices outside of the
itself. microcontroller itself.
* `microbit-v2` includes a simple driver for the LED matrix. - `microbit-v2` includes a simple driver for the LED matrix.
Run the example with: Run the example with:

@ -1,7 +1,9 @@
# Debugging # Debugging
_Embed.toml_: _Embed.toml_:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```toml ```toml
[default.general] [default.general]
chip = "nrf52833_xxAA" chip = "nrf52833_xxAA"
@ -11,7 +13,9 @@ enabled = true
``` ```
In one terminal under `src/bare-metal/microcontrollers/examples/`: In one terminal under `src/bare-metal/microcontrollers/examples/`:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```sh ```sh
cargo embed --bin board_support debug cargo embed --bin board_support debug
``` ```
@ -19,20 +23,27 @@ cargo embed --bin board_support debug
In another terminal in the same directory: In another terminal in the same directory:
On gLinux or Debian: On gLinux or Debian:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```sh ```sh
gdb-multiarch target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337" gdb-multiarch target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"
``` ```
On MacOS: On MacOS:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```sh ```sh
arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337" arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"
``` ```
<details> <details>
In GDB, try running: In GDB, try running:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```gdb ```gdb
b src/bin/board_support.rs:29 b src/bin/board_support.rs:29
b src/bin/board_support.rs:30 b src/bin/board_support.rs:30

@ -1,23 +1,25 @@
# `embedded-hal` # `embedded-hal`
The [`embedded-hal`](https://crates.io/crates/embedded-hal) crate provides a number of traits The [`embedded-hal`](https://crates.io/crates/embedded-hal) crate provides a
covering common microcontroller peripherals. number of traits covering common microcontroller peripherals.
* GPIO - GPIO
* ADC - ADC
* I2C, SPI, UART, CAN - I2C, SPI, UART, CAN
* RNG - RNG
* Timers - Timers
* Watchdogs - Watchdogs
Other crates then implement Other crates then implement
[drivers](https://github.com/rust-embedded/awesome-embedded-rust#driver-crates) in terms of these [drivers](https://github.com/rust-embedded/awesome-embedded-rust#driver-crates)
traits, e.g. an accelerometer driver might need an I2C or SPI bus implementation. in terms of these traits, e.g. an accelerometer driver might need an I2C or SPI
bus implementation.
<details> <details>
* There are implementations for many microcontrollers, as well as other platforms such as Linux on - There are implementations for many microcontrollers, as well as other
Raspberry Pi. platforms such as Linux on Raspberry Pi.
* There is work in progress on an `async` version of `embedded-hal`, but it isn't stable yet. - There is work in progress on an `async` version of `embedded-hal`, but it
isn't stable yet.
</details> </details>

@ -47,10 +47,18 @@ fn main() -> ! {
// no aliases exist. // no aliases exist.
unsafe { unsafe {
pin_cnf_21.write_volatile( pin_cnf_21.write_volatile(
DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, DIR_OUTPUT
| INPUT_DISCONNECT
| PULL_DISABLED
| DRIVE_S0S1
| SENSE_DISABLED,
); );
pin_cnf_28.write_volatile( pin_cnf_28.write_volatile(
DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, DIR_OUTPUT
| INPUT_DISCONNECT
| PULL_DISABLED
| DRIVE_S0S1
| SENSE_DISABLED,
); );
} }

@ -20,7 +20,8 @@ extern crate panic_halt as _;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use nrf52833_hal::gpio::p0::{self, P0_01, P0_02, P0_03}; use nrf52833_hal::gpio::p0::{self, P0_01, P0_02, P0_03};
use nrf52833_hal::gpio::{ use nrf52833_hal::gpio::{
Disconnected, Floating, Input, Level, OpenDrain, OpenDrainConfig, Output, PushPull, Disconnected, Floating, Input, Level, OpenDrain, OpenDrainConfig, Output,
PushPull,
}; };
use nrf52833_hal::pac::Peripherals; use nrf52833_hal::pac::Peripherals;
use nrf52833_hal::prelude::*; use nrf52833_hal::prelude::*;
@ -46,7 +47,8 @@ fn main() -> ! {
let _pin2: P0_02<Output<OpenDrain>> = gpio0 let _pin2: P0_02<Output<OpenDrain>> = gpio0
.p0_02 .p0_02
.into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low); .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low);
let _pin3: P0_03<Output<PushPull>> = gpio0.p0_03.into_push_pull_output(Level::Low); let _pin3: P0_03<Output<PushPull>> =
gpio0.p0_03.into_push_pull_output(Level::Low);
loop {} loop {}
} }

@ -1,8 +1,9 @@
# HAL crates # HAL crates
[HAL crates](https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates) for [HAL crates](https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates)
many microcontrollers provide wrappers around various peripherals. These generally implement traits for many microcontrollers provide wrappers around various peripherals. These
from [`embedded-hal`](https://crates.io/crates/embedded-hal). generally implement traits from
[`embedded-hal`](https://crates.io/crates/embedded-hal).
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/bin/hal.rs:Example}} {{#include examples/src/bin/hal.rs:Example}}
@ -10,9 +11,9 @@ from [`embedded-hal`](https://crates.io/crates/embedded-hal).
<details> <details>
* `set_low` and `set_high` are methods on the `embedded_hal` `OutputPin` trait. - `set_low` and `set_high` are methods on the `embedded_hal` `OutputPin` trait.
* HAL crates exist for many Cortex-M and RISC-V devices, including various STM32, GD32, nRF, NXP, - HAL crates exist for many Cortex-M and RISC-V devices, including various
MSP430, AVR and PIC microcontrollers. STM32, GD32, nRF, NXP, MSP430, AVR and PIC microcontrollers.
Run the example with: Run the example with:

@ -1,7 +1,7 @@
# Raw MMIO # Raw MMIO
Most microcontrollers access peripherals via memory-mapped IO. Let's try turning on an LED on our Most microcontrollers access peripherals via memory-mapped IO. Let's try turning
micro:bit: on an LED on our micro:bit:
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/bin/mmio.rs:Example}} {{#include examples/src/bin/mmio.rs:Example}}
@ -9,7 +9,8 @@ micro:bit:
<details> <details>
* GPIO 0 pin 21 is connected to the first column of the LED matrix, and pin 28 to the first row. - GPIO 0 pin 21 is connected to the first column of the LED matrix, and pin 28
to the first row.
Run the example with: Run the example with:

@ -1,26 +1,29 @@
# Other projects # Other projects
* [RTIC](https://rtic.rs/) - [RTIC](https://rtic.rs/)
* "Real-Time Interrupt-driven Concurrency" - "Real-Time Interrupt-driven Concurrency"
* Shared resource management, message passing, task scheduling, timer queue - Shared resource management, message passing, task scheduling, timer queue
* [Embassy](https://embassy.dev/) - [Embassy](https://embassy.dev/)
* `async` executors with priorities, timers, networking, USB - `async` executors with priorities, timers, networking, USB
* [TockOS](https://www.tockos.org/documentation/getting-started) - [TockOS](https://www.tockos.org/documentation/getting-started)
* Security-focused RTOS with preemptive scheduling and Memory Protection Unit support - Security-focused RTOS with preemptive scheduling and Memory Protection Unit
* [Hubris](https://hubris.oxide.computer/) support
* Microkernel RTOS from Oxide Computer Company with memory protection, unprivileged drivers, IPC - [Hubris](https://hubris.oxide.computer/)
* [Bindings for FreeRTOS](https://github.com/lobaro/FreeRTOS-rust) - Microkernel RTOS from Oxide Computer Company with memory protection,
* Some platforms have `std` implementations, e.g. unprivileged drivers, IPC
- [Bindings for FreeRTOS](https://github.com/lobaro/FreeRTOS-rust)
- Some platforms have `std` implementations, e.g.
[esp-idf](https://esp-rs.github.io/book/overview/using-the-standard-library.html). [esp-idf](https://esp-rs.github.io/book/overview/using-the-standard-library.html).
<details> <details>
* RTIC can be considered either an RTOS or a concurrency framework. - RTIC can be considered either an RTOS or a concurrency framework.
* It doesn't include any HALs. - It doesn't include any HALs.
* It uses the Cortex-M NVIC (Nested Virtual Interrupt Controller) for scheduling rather than a - It uses the Cortex-M NVIC (Nested Virtual Interrupt Controller) for
proper kernel. scheduling rather than a proper kernel.
* Cortex-M only. - Cortex-M only.
* Google uses TockOS on the Haven microcontroller for Titan security keys. - Google uses TockOS on the Haven microcontroller for Titan security keys.
* FreeRTOS is mostly written in C, but there are Rust bindings for writing applications. - FreeRTOS is mostly written in C, but there are Rust bindings for writing
applications.
</details> </details>

@ -1,8 +1,8 @@
# Peripheral Access Crates # Peripheral Access Crates
[`svd2rust`](https://crates.io/crates/svd2rust) generates mostly-safe Rust wrappers for [`svd2rust`](https://crates.io/crates/svd2rust) generates mostly-safe Rust
memory-mapped peripherals from [CMSIS-SVD](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html) wrappers for memory-mapped peripherals from
files. [CMSIS-SVD](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html) files.
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include examples/src/bin/pac.rs:Example}} {{#include examples/src/bin/pac.rs:Example}}
@ -10,15 +10,17 @@ files.
<details> <details>
* SVD (System View Description) files are XML files typically provided by silicon vendors which - SVD (System View Description) files are XML files typically provided by
describe the memory map of the device. silicon vendors which describe the memory map of the device.
* They are organised by peripheral, register, field and value, with names, descriptions, addresses - They are organised by peripheral, register, field and value, with names,
and so on. descriptions, addresses and so on.
* SVD files are often buggy and incomplete, so there are various projects which patch the - SVD files are often buggy and incomplete, so there are various projects
mistakes, add missing details, and publish the generated crates. which patch the mistakes, add missing details, and publish the generated
* `cortex-m-rt` provides the vector table, among other things. crates.
* If you `cargo install cargo-binutils` then you can run - `cortex-m-rt` provides the vector table, among other things.
`cargo objdump --bin pac -- -d --no-show-raw-insn` to see the resulting binary. - If you `cargo install cargo-binutils` then you can run
`cargo objdump --bin pac -- -d --no-show-raw-insn` to see the resulting
binary.
Run the example with: Run the example with:

@ -1,11 +1,11 @@
# `probe-rs` and `cargo-embed` # `probe-rs` and `cargo-embed`
[probe-rs](https://probe.rs/) is a handy toolset for embedded debugging, like OpenOCD but better [probe-rs](https://probe.rs/) is a handy toolset for embedded debugging, like
integrated. OpenOCD but better integrated.
* SWD (Serial Wire Debug) and JTAG via CMSIS-DAP, ST-Link and J-Link probes - SWD (Serial Wire Debug) and JTAG via CMSIS-DAP, ST-Link and J-Link probes
* GDB stub and Microsoft DAP (Debug Adapter Protocol) server - GDB stub and Microsoft DAP (Debug Adapter Protocol) server
* Cargo integration - Cargo integration
`cargo-embed` is a cargo subcommand to build and flash binaries, log RTT (Real `cargo-embed` is a cargo subcommand to build and flash binaries, log RTT (Real
Time Transfers) output and connect GDB. It's configured by an `Embed.toml` file Time Transfers) output and connect GDB. It's configured by an `Embed.toml` file
@ -13,17 +13,22 @@ in your project directory.
<details> <details>
* [CMSIS-DAP](https://arm-software.github.io/CMSIS_5/DAP/html/index.html) is an Arm standard - [CMSIS-DAP](https://arm-software.github.io/CMSIS_5/DAP/html/index.html) is an
protocol over USB for an in-circuit debugger to access the CoreSight Debug Access Port of various Arm standard protocol over USB for an in-circuit debugger to access the
Arm Cortex processors. It's what the on-board debugger on the BBC micro:bit uses. CoreSight Debug Access Port of various Arm Cortex processors. It's what the
* ST-Link is a range of in-circuit debuggers from ST Microelectronics, J-Link is a range from on-board debugger on the BBC micro:bit uses.
SEGGER. - ST-Link is a range of in-circuit debuggers from ST Microelectronics, J-Link is
* The Debug Access Port is usually either a 5-pin JTAG interface or 2-pin Serial Wire Debug. a range from SEGGER.
* probe-rs is a library which you can integrate into your own tools if you want to. - The Debug Access Port is usually either a 5-pin JTAG interface or 2-pin Serial
* The [Microsoft Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) lets Wire Debug.
VSCode and other IDEs debug code running on any supported microcontroller. - probe-rs is a library which you can integrate into your own tools if you want
* cargo-embed is a binary built using the probe-rs library. to.
* RTT (Real Time Transfers) is a mechanism to transfer data between the debug host and the target - The
through a number of ringbuffers. [Microsoft Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
lets VSCode and other IDEs debug code running on any supported
microcontroller.
- cargo-embed is a binary built using the probe-rs library.
- RTT (Real Time Transfers) is a mechanism to transfer data between the debug
host and the target through a number of ringbuffers.
</details> </details>

@ -6,15 +6,17 @@
<details> <details>
* Pins don't implement `Copy` or `Clone`, so only one instance of each can exist. Once a pin is - Pins don't implement `Copy` or `Clone`, so only one instance of each can
moved out of the port struct nobody else can take it. exist. Once a pin is moved out of the port struct nobody else can take it.
* Changing the configuration of a pin consumes the old pin instance, so you can’t keep use the old - Changing the configuration of a pin consumes the old pin instance, so you
instance afterwards. can’t keep use the old instance afterwards.
* The type of a value indicates the state that it is in: e.g. in this case, the configuration state - The type of a value indicates the state that it is in: e.g. in this case, the
of a GPIO pin. This encodes the state machine into the type system, and ensures that you don't configuration state of a GPIO pin. This encodes the state machine into the
try to use a pin in a certain way without properly configuring it first. Illegal state type system, and ensures that you don't try to use a pin in a certain way
transitions are caught at compile time. without properly configuring it first. Illegal state transitions are caught at
* You can call `is_high` on an input pin and `set_high` on an output pin, but not vice-versa. compile time.
* Many HAL crates follow this pattern. - You can call `is_high` on an input pin and `set_high` on an output pin, but
not vice-versa.
- Many HAL crates follow this pattern.
</details> </details>

@ -1,5 +1,7 @@
# A minimal `no_std` program # A minimal `no_std` program
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
#![no_main] #![no_main]
#![no_std] #![no_std]
@ -14,13 +16,13 @@ fn panic(_panic: &PanicInfo) -> ! {
<details> <details>
* This will compile to an empty binary. - This will compile to an empty binary.
* `std` provides a panic handler; without it we must provide our own. - `std` provides a panic handler; without it we must provide our own.
* It can also be provided by another crate, such as `panic-halt`. - It can also be provided by another crate, such as `panic-halt`.
* Depending on the target, you may need to compile with `panic = "abort"` to avoid an error about - Depending on the target, you may need to compile with `panic = "abort"` to
`eh_personality`. avoid an error about `eh_personality`.
* Note that there is no `main` or any other entry point; it's up to you to define your own entry - Note that there is no `main` or any other entry point; it's up to you to
point. This will typically involve a linker script and some assembly code to set things up ready define your own entry point. This will typically involve a linker script and
for Rust code to run. some assembly code to set things up ready for Rust code to run.
</details> </details>

@ -21,37 +21,37 @@
<tr valign="top"> <tr valign="top">
<td> <td>
* Slices, `&str`, `CStr` - Slices, `&str`, `CStr`
* `NonZeroU8`... - `NonZeroU8`...
* `Option`, `Result` - `Option`, `Result`
* `Display`, `Debug`, `write!`... - `Display`, `Debug`, `write!`...
* `Iterator` - `Iterator`
* `panic!`, `assert_eq!`... - `panic!`, `assert_eq!`...
* `NonNull` and all the usual pointer-related functions - `NonNull` and all the usual pointer-related functions
* `Future` and `async`/`await` - `Future` and `async`/`await`
* `fence`, `AtomicBool`, `AtomicPtr`, `AtomicU32`... - `fence`, `AtomicBool`, `AtomicPtr`, `AtomicU32`...
* `Duration` - `Duration`
</td> </td>
<td> <td>
* `Box`, `Cow`, `Arc`, `Rc` - `Box`, `Cow`, `Arc`, `Rc`
* `Vec`, `BinaryHeap`, `BtreeMap`, `LinkedList`, `VecDeque` - `Vec`, `BinaryHeap`, `BtreeMap`, `LinkedList`, `VecDeque`
* `String`, `CString`, `format!` - `String`, `CString`, `format!`
</td> </td>
<td> <td>
* `Error` - `Error`
* `HashMap` - `HashMap`
* `Mutex`, `Condvar`, `Barrier`, `Once`, `RwLock`, `mpsc` - `Mutex`, `Condvar`, `Barrier`, `Once`, `RwLock`, `mpsc`
* `File` and the rest of `fs` - `File` and the rest of `fs`
* `println!`, `Read`, `Write`, `Stdin`, `Stdout` and the rest of `io` - `println!`, `Read`, `Write`, `Stdin`, `Stdout` and the rest of `io`
* `Path`, `OsString` - `Path`, `OsString`
* `net` - `net`
* `Command`, `Child`, `ExitCode` - `Command`, `Child`, `ExitCode`
* `spawn`, `sleep` and the rest of `thread` - `spawn`, `sleep` and the rest of `thread`
* `SystemTime`, `Instant` - `SystemTime`, `Instant`
</td> </td>
</tr> </tr>
@ -59,7 +59,7 @@
<details> <details>
* `HashMap` depends on RNG. - `HashMap` depends on RNG.
* `std` re-exports the contents of both `core` and `alloc`. - `std` re-exports the contents of both `core` and `alloc`.
</details> </details>

@ -1,3 +1,4 @@
# Useful crates # Useful crates
We'll go over a few crates which solve some common problems in bare-metal programming. We'll go over a few crates which solve some common problems in bare-metal
programming.

@ -1,7 +1,7 @@
# `aarch64-paging` # `aarch64-paging`
The [`aarch64-paging`][1] crate lets you create page tables according to the AArch64 Virtual Memory The [`aarch64-paging`][1] crate lets you create page tables according to the
System Architecture. AArch64 Virtual Memory System Architecture.
```rust,editable,compile_fail ```rust,editable,compile_fail
use aarch64_paging::{ use aarch64_paging::{
@ -25,10 +25,11 @@ idmap.activate();
<details> <details>
* For now it only supports EL1, but support for other exception levels should be straightforward to - For now it only supports EL1, but support for other exception levels should be
add. straightforward to add.
* This is used in Android for the [Protected VM Firmware][2]. - This is used in Android for the [Protected VM Firmware][2].
* There's no easy way to run this example, as it needs to run on real hardware or under QEMU. - There's no easy way to run this example, as it needs to run on real hardware
or under QEMU.
</details> </details>

@ -1,19 +1,23 @@
# `buddy_system_allocator` # `buddy_system_allocator`
[`buddy_system_allocator`][1] is a third-party crate implementing a basic buddy system allocator. [`buddy_system_allocator`][1] is a third-party crate implementing a basic buddy
It can be used both for [`LockedHeap`][2] implementing [`GlobalAlloc`][3] so you can use the system allocator. It can be used both for [`LockedHeap`][2] implementing
standard `alloc` crate (as we saw [before][4]), or for allocating other address space. For example, [`GlobalAlloc`][3] so you can use the standard `alloc` crate (as we saw
we might want to allocate MMIO space for PCI BARs: [before][4]), or for allocating other address space. For example, we might want
to allocate MMIO space for PCI BARs:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include allocator-example/src/main.rs:main}} {{#include allocator-example/src/main.rs:main}}
``` ```
<details> <details>
* PCI BARs always have alignment equal to their size. - PCI BARs always have alignment equal to their size.
* Run the example with `cargo run` under `src/bare-metal/useful-crates/allocator-example/`. (It won't - Run the example with `cargo run` under
run in the Playground because of the crate dependency.) `src/bare-metal/useful-crates/allocator-example/`. (It won't run in the
Playground because of the crate dependency.)
</details> </details>

@ -1,11 +1,14 @@
# `spin` # `spin`
`std::sync::Mutex` and the other synchronisation primitives from `std::sync` are not available in `std::sync::Mutex` and the other synchronisation primitives from `std::sync` are
`core` or `alloc`. How can we manage synchronisation or interior mutability, such as for sharing not available in `core` or `alloc`. How can we manage synchronisation or
state between different CPUs? interior mutability, such as for sharing state between different CPUs?
The [`spin`][1] crate provides spinlock-based equivalents of many of these
primitives.
The [`spin`][1] crate provides spinlock-based equivalents of many of these primitives.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
use spin::mutex::SpinMutex; use spin::mutex::SpinMutex;
@ -20,12 +23,12 @@ fn main() {
<details> <details>
* Be careful to avoid deadlock if you take locks in interrupt handlers. - Be careful to avoid deadlock if you take locks in interrupt handlers.
* `spin` also has a ticket lock mutex implementation; equivalents of `RwLock`, `Barrier` and `Once` - `spin` also has a ticket lock mutex implementation; equivalents of `RwLock`,
from `std::sync`; and `Lazy` for lazy initialisation. `Barrier` and `Once` from `std::sync`; and `Lazy` for lazy initialisation.
* The [`once_cell`][2] crate also has some useful types for late initialisation with a slightly - The [`once_cell`][2] crate also has some useful types for late initialisation
different approach to `spin::once::Once`. with a slightly different approach to `spin::once::Once`.
* The Rust Playground includes `spin`, so this example will run fine inline. - The Rust Playground includes `spin`, so this example will run fine inline.
</details> </details>

@ -1,10 +1,12 @@
# `tinyvec` # `tinyvec`
Sometimes you want something which can be resized like a `Vec`, but without heap allocation. Sometimes you want something which can be resized like a `Vec`, but without heap
[`tinyvec`][1] provides this: a vector backed by an array or slice, which could be statically allocation. [`tinyvec`][1] provides this: a vector backed by an array or slice,
allocated or on the stack, which keeps track of how many elements are used and panics if you try to which could be statically allocated or on the stack, which keeps track of how
use more than are allocated. many elements are used and panics if you try to use more than are allocated.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
use tinyvec::{array_vec, ArrayVec}; use tinyvec::{array_vec, ArrayVec};
@ -20,8 +22,9 @@ fn main() {
<details> <details>
* `tinyvec` requires that the element type implement `Default` for initialisation. - `tinyvec` requires that the element type implement `Default` for
* The Rust Playground includes `tinyvec`, so this example will run fine inline. initialisation.
- The Rust Playground includes `tinyvec`, so this example will run fine inline.
</details> </details>

@ -1,24 +1,29 @@
# `zerocopy` # `zerocopy`
The [`zerocopy`][1] crate (from Fuchsia) provides traits and macros for safely converting between The [`zerocopy`][1] crate (from Fuchsia) provides traits and macros for safely
byte sequences and other types. converting between byte sequences and other types.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
{{#include zerocopy-example/src/main.rs:main}} {{#include zerocopy-example/src/main.rs:main}}
``` ```
This is not suitable for MMIO (as it doesn't use volatile reads and writes), but can be useful for This is not suitable for MMIO (as it doesn't use volatile reads and writes), but
working with structures shared with hardware e.g. by DMA, or sent over some external interface. can be useful for working with structures shared with hardware e.g. by DMA, or
sent over some external interface.
<details> <details>
* `FromBytes` can be implemented for types for which any byte pattern is valid, and so can safely be - `FromBytes` can be implemented for types for which any byte pattern is valid,
converted from an untrusted sequence of bytes. and so can safely be converted from an untrusted sequence of bytes.
* Attempting to derive `FromBytes` for these types would fail, because `RequestType` doesn't use all - Attempting to derive `FromBytes` for these types would fail, because
possible u32 values as discriminants, so not all byte patterns are valid. `RequestType` doesn't use all possible u32 values as discriminants, so not all
* `zerocopy::byteorder` has types for byte-order aware numeric primitives. byte patterns are valid.
* Run the example with `cargo run` under `src/bare-metal/useful-crates/zerocopy-example/`. (It won't - `zerocopy::byteorder` has types for byte-order aware numeric primitives.
run in the Playground because of the crate dependency.) - Run the example with `cargo run` under
`src/bare-metal/useful-crates/zerocopy-example/`. (It won't run in the
Playground because of the crate dependency.)
</details> </details>

@ -4,12 +4,14 @@ minutes: 10
# Borrow Checking # Borrow Checking
Rust's _borrow checker_ puts constraints on the ways you can borrow values. For a given value, at any time: Rust's _borrow checker_ puts constraints on the ways you can borrow values. For
a given value, at any time:
* You can have one or more shared references to the value, _or_ - You can have one or more shared references to the value, _or_
* You can have exactly one exclusive reference to the value. - You can have exactly one exclusive reference to the value.
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail ```rust,editable,compile_fail
fn main() { fn main() {
let mut a: i32 = 10; let mut a: i32 = 10;
@ -27,11 +29,22 @@ fn main() {
<details> <details>
* Note that the requirement is that conflicting references not _exist_ at the same point. It does not matter where the reference is dereferenced. - Note that the requirement is that conflicting references not _exist_ at the
* The above code does not compile because `a` is borrowed as mutable (through `c`) and as immutable (through `b`) at the same time. same point. It does not matter where the reference is dereferenced.
* Move the `println!` statement for `b` before the scope that introduces `c` to make the code compile. - The above code does not compile because `a` is borrowed as mutable (through
* After that change, the compiler realizes that `b` is only ever used before the new mutable borrow of `a` through `c`. This is a feature of the borrow checker called "non-lexical lifetimes". `c`) and as immutable (through `b`) at the same time.
* The exclusive reference constraint is quite strong. Rust uses it to ensure that data races do not occur. Rust also _relies_ on this constraint to optimize code. For example, a value behind a shared reference can be safely cached in a register for the lifetime of that reference. - Move the `println!` statement for `b` before the scope that introduces `c` to
* The borrow checker is designed to accommodate many common patterns, such as taking exclusive references to different fields in a struct at the same time. But, there are some situations where it doesn't quite "get it" and this often results in "fighting with the borrow checker." make the code compile.
- After that change, the compiler realizes that `b` is only ever used before the
new mutable borrow of `a` through `c`. This is a feature of the borrow checker
called "non-lexical lifetimes".
- The exclusive reference constraint is quite strong. Rust uses it to ensure
that data races do not occur. Rust also _relies_ on this constraint to
optimize code. For example, a value behind a shared reference can be safely
cached in a register for the lifetime of that reference.
- The borrow checker is designed to accommodate many common patterns, such as
taking exclusive references to different fields in a struct at the same time.
But, there are some situations where it doesn't quite "get it" and this often
results in "fighting with the borrow checker."
</details> </details>

@ -5,6 +5,7 @@ minutes: 10
<!-- NOTES: <!-- NOTES:
Introduce the concept, with an example based on Mutex showing an `&self` method doing mutation; reference Cell/RefCell without detail. Introduce the concept, with an example based on Mutex showing an `&self` method doing mutation; reference Cell/RefCell without detail.
--> -->
# Interior Mutability # Interior Mutability
Rust provides a few safe means of modifying a value given only a shared Rust provides a few safe means of modifying a value given only a shared
@ -15,7 +16,7 @@ checks.
[`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) and [`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) and
[`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) implement [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) implement
what Rust calls *interior mutability:* mutation of values in an immutable what Rust calls _interior mutability:_ mutation of values in an immutable
context. context.
`Cell` is typically used for simple types, as it requires copying or moving `Cell` is typically used for simple types, as it requires copying or moving
@ -57,9 +58,17 @@ fn main() {
<details> <details>
* If we were using `Cell` instead of `RefCell` in this example, we would have to move the `Node` out of the `Rc` to push children, then move it back in. This is safe because there's always one, un-referenced value in the cell, but it's not ergonomic. - If we were using `Cell` instead of `RefCell` in this example, we would have to
* To do anything with a Node, you must call a `RefCell` method, usually `borrow` or `borrow_mut`. move the `Node` out of the `Rc` to push children, then move it back in. This
* Demonstrate that reference loops can be created by adding `root` to `subtree.children` (don't try to print it!). is safe because there's always one, un-referenced value in the cell, but it's
* To demonstrate a runtime panic, add a `fn inc(&mut self)` that increments `self.value` and calls the same method on its children. This will panic in the presence of the reference loop, with `thread 'main' panicked at 'already borrowed: BorrowMutError'`. not ergonomic.
- To do anything with a Node, you must call a `RefCell` method, usually `borrow`
or `borrow_mut`.
- Demonstrate that reference loops can be created by adding `root` to
`subtree.children` (don't try to print it!).
- To demonstrate a runtime panic, add a `fn inc(&mut self)` that increments
`self.value` and calls the same method on its children. This will panic in the
presence of the reference loop, with
`thread 'main' panicked at 'already borrowed: BorrowMutError'`.
</details> </details>

@ -4,10 +4,11 @@ minutes: 10
# Borrowing a Value # Borrowing a Value
As we saw before, instead of transferring ownership when calling a function, As we saw before, instead of transferring ownership when calling a function, you
you can let a function _borrow_ the value: can let a function _borrow_ the value:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable ```rust,editable
#[derive(Debug)] #[derive(Debug)]
struct Point(i32, i32); struct Point(i32, i32);
@ -24,8 +25,8 @@ fn main() {
} }
``` ```
* The `add` function _borrows_ two points and returns a new point. - The `add` function _borrows_ two points and returns a new point.
* The caller retains ownership of the inputs. - The caller retains ownership of the inputs.
<details> <details>
@ -36,7 +37,12 @@ slightly to include function arguments and return values.
Notes on stack returns: Notes on stack returns:
* Demonstrate that the return from `add` is cheap because the compiler can eliminate the copy operation. Change the above code to print stack addresses and run it on the [Playground] or look at the assembly in [Godbolt](https://rust.godbolt.org/). In the "DEBUG" optimization level, the addresses should change, while they stay the same when changing to the "RELEASE" setting: - Demonstrate that the return from `add` is cheap because the compiler can
eliminate the copy operation. Change the above code to print stack addresses
and run it on the [Playground] or look at the assembly in
[Godbolt](https://rust.godbolt.org/). In the "DEBUG" optimization level, the
addresses should change, while they stay the same when changing to the
"RELEASE" setting:
<!-- mdbook-xgettext: skip --> <!-- mdbook-xgettext: skip -->
```rust,editable ```rust,editable
@ -57,8 +63,11 @@ Notes on stack returns:
println!("{p1:?} + {p2:?} = {p3:?}"); println!("{p1:?} + {p2:?} = {p3:?}");
} }
``` ```
* The Rust compiler can do return value optimization (RVO). - The Rust compiler can do return value optimization (RVO).
* In C++, copy elision has to be defined in the language specification because constructors can have side effects. In Rust, this is not an issue at all. If RVO did not happen, Rust will always perform a simple and efficient `memcpy` copy. - In C++, copy elision has to be defined in the language specification because
constructors can have side effects. In Rust, this is not an issue at all. If
RVO did not happen, Rust will always perform a simple and efficient `memcpy`
copy.
</details> </details>

@ -1,21 +1,30 @@
# Using Cargo # Using Cargo
When you start reading about Rust, you will soon meet [Cargo](https://doc.rust-lang.org/cargo/), the standard tool When you start reading about Rust, you will soon meet
used in the Rust ecosystem to build and run Rust applications. Here we want to [Cargo](https://doc.rust-lang.org/cargo/), the standard tool used in the Rust
give a brief overview of what Cargo is and how it fits into the wider ecosystem ecosystem to build and run Rust applications. Here we want to give a brief
and how it fits into this training. overview of what Cargo is and how it fits into the wider ecosystem and how it
fits into this training.
## Installation ## Installation
> **Please follow the instructions on <https://rustup.rs/>.** > **Please follow the instructions on <https://rustup.rs/>.**
This will give you the Cargo build tool (`cargo`) and the Rust compiler (`rustc`). You will also get `rustup`, a command line utility that you can use to install to different compiler versions. This will give you the Cargo build tool (`cargo`) and the Rust compiler
(`rustc`). You will also get `rustup`, a command line utility that you can use
to install to different compiler versions.
After installing Rust, you should configure your editor or IDE to work with Rust. Most editors do this by talking to [rust-analyzer], which provides auto-completion and jump-to-definition functionality for [VS Code], [Emacs], [Vim/Neovim], and many others. There is also a different IDE available called [RustRover]. After installing Rust, you should configure your editor or IDE to work with
Rust. Most editors do this by talking to [rust-analyzer], which provides
auto-completion and jump-to-definition functionality for [VS Code], [Emacs],
[Vim/Neovim], and many others. There is also a different IDE available called
[RustRover].
<details> <details>
* On Debian/Ubuntu, you can also install Cargo, the Rust source and the [Rust formatter] via `apt`. However, this gets you an outdated rust version and may lead to unexpected behavior. The command would be: - On Debian/Ubuntu, you can also install Cargo, the Rust source and the
[Rust formatter] via `apt`. However, this gets you an outdated rust version
and may lead to unexpected behavior. The command would be:
```shell ```shell
sudo apt install cargo rust-src rustfmt sudo apt install cargo rust-src rustfmt

@ -21,15 +21,14 @@ text box.
<details> <details>
Most code samples are editable like shown above. A few code samples Most code samples are editable like shown above. A few code samples are not
are not editable for various reasons: editable for various reasons:
* The embedded playgrounds cannot execute unit tests. Copy-paste the - The embedded playgrounds cannot execute unit tests. Copy-paste the code and
code and open it in the real Playground to demonstrate unit tests. open it in the real Playground to demonstrate unit tests.
* The embedded playgrounds lose their state the moment you navigate - The embedded playgrounds lose their state the moment you navigate away from
away from the page! This is the reason that the students should the page! This is the reason that the students should solve the exercises
solve the exercises using a local Rust installation or via the using a local Rust installation or via the Playground.
Playground.
</details> </details>

Some files were not shown because too many files have changed in this diff Show More