mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-08 17:03:53 +02:00
Format all Markdown files with dprint
(#1157)
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 dprint/check#11 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:
parent
f43e72e0ad
commit
c9f66fd425
dprint.json
mdbook-course/src
mdbook-exerciser/src
po
rustfmt.tomlsrc
SUMMARY.mdandroid.md
android
aidl.mdsetup.md
async.mdaidl
build-rules.mdbuild-rules
interoperability.mdinteroperability
cpp
android-build-cpp.mdandroid-cpp-genrules.mdbridge.mdcpp-bridge.mdcpp-exception.mdrust-bridge.mdrust-result.mdshared-enums.mdshared-types.mdtype-mapping.md
java.mdwith-c.mdwith-c
async
bare-metal.mdbare-metal
alloc-example/src
alloc.mdandroid.mdandroid
aps.mdaps
better-uart.md
microcontrollers.mdbetter-uart
entry-point.mdexamples/src
exceptions.mdinline-assembly.mdlogging.mdlogging
mmio.mdother-projects.mduart.mduart
microcontrollers
board-support.mddebugging.mdembedded-hal.md
minimal.mdno_std.mduseful-crates.mdexamples/src/bin
hals.mdmmio.mdother-projects.mdpacs.mdprobe-rs.mdtype-state.mduseful-crates
borrowing
cargo.mdcargo
@ -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)) =
|
||||||
(¤t_course_name, ¤t_session_name)
|
(¤t_course_name, ¤t_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:?}\""
|
||||||
|
10
rustfmt.toml
10
rustfmt.toml
@ -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>
|
||||||
|
|
||||||
|
23
src/cargo.md
23
src/cargo.md
@ -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
Loading…
x
Reference in New Issue
Block a user