diff --git a/book.toml b/book.toml index c5a99ea38ccc..32e858438f91 100644 --- a/book.toml +++ b/book.toml @@ -13,8 +13,8 @@ class = "bob" [output.html] curly-quotes = true -additional-js = ["ga4.js"] -additional-css = ["svgbob.css"] +additional-js = ["ga4.js", "speaker-notes.js"] +additional-css = ["svgbob.css", "speaker-notes.css"] git-repository-url = "https://github.com/google/comprehensive-rust" edit-url-template = "https://github.com/google/comprehensive-rust/edit/main/{path}" diff --git a/speaker-notes.css b/speaker-notes.css new file mode 100644 index 000000000000..43d8f3e9015f --- /dev/null +++ b/speaker-notes.css @@ -0,0 +1,24 @@ +.content details { + background: var(--sidebar-bg); + color: var(--sidebar-fg) !important; + border-radius: 0.25em; + padding: 0.25em; +} + +.content details summary h4 { + display: inline-block; + list-style: none; + font-weight: normal; + font-style: italic; + margin: 0.5em 0.25em; + cursor: pointer; +} + +.content details summary h4:target::before { + margin-left: -40px; + width: 40px; +} + +.content details summary a { + margin-left: 0.5em; +} diff --git a/speaker-notes.js b/speaker-notes.js new file mode 100644 index 000000000000..ff92ba576e62 --- /dev/null +++ b/speaker-notes.js @@ -0,0 +1,226 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +(function() { + let notes = document.querySelector("details"); + // Create an unattached DOM node for the code below. + if (!notes) { + notes = document.createElement("details"); + } + let popIn = document.createElement("button"); + + // Mark the speaker note window defunct. This means that it will no longer + // show the notes. + function markDefunct() { + const main = document.querySelector("main"); + const h4 = document.createElement("h4"); + h4.append("(You can close this window now.)"); + main.replaceChildren(h4); + window.location.hash = "#speaker-notes-defunct"; + } + + // Update the window. This shows/hides controls as necessary for regular and + // speaker note pages. + function applyState() { + if (window.location.hash == "#speaker-notes-open") { + if (getState() != "popup") { + markDefunct(); + } + return; + } + + switch (getState()) { + case "popup": + popIn.classList.remove("hidden"); + notes.classList.add("hidden"); + break; + case "inline-open": + popIn.classList.add("hidden"); + notes.open = true; + notes.classList.remove("hidden"); + break; + case "inline-closed": + popIn.classList.add("hidden"); + notes.open = false; + notes.classList.remove("hidden"); + break; + } + } + + // Get the state of the speaker note window: "inline-open", "inline-closed", + // or "popup". + function getState() { + return window.localStorage["speakerNotes"] || "inline-open"; + } + + // Set the state of the speaker note window. Call applyState as needed + // afterwards. + function setState(state) { + window.localStorage["speakerNotes"] = state; + } + + // Create controls for a regular page. + function setupRegularPage() { + // Create pop-in button. + popIn.setAttribute("id", "speaker-notes-toggle"); + popIn.setAttribute("type", "button"); + popIn.setAttribute("title", "Close speaker notes"); + popIn.setAttribute("aria-label", "Close speaker notes"); + popIn.classList.add("icon-button"); + let i = document.createElement("i"); + i.classList.add("fa", "fa-window-close-o"); + popIn.append(i); + popIn.addEventListener("click", (event) => { + setState("inline-open"); + applyState(); + }); + document.querySelector(".left-buttons").append(popIn); + + // Create speaker notes. + notes.addEventListener("toggle", (event) => { + setState(notes.open ? "inline-open" : "inline-closed"); + }); + + let summary = document.createElement("summary"); + notes.insertBefore(summary, notes.firstChild); + + let h4 = document.createElement("h4"); + h4.setAttribute("id", "speaker-notes"); + h4.append("Speaker Notes"); + h4.addEventListener("click", (event) => { + // Update fragment as if we had clicked a link. A regular a element would + // result in double-fire of the event. + window.location.hash = "#speaker-notes"; + }); + summary.append(h4); + + // Create pop-out button. + let popOutLocation = new URL(window.location.href); + popOutLocation.hash = "#speaker-notes-open"; + let popOut = document.createElement("a"); + popOut.setAttribute("href", popOutLocation.href); + popOut.setAttribute("target", "speakerNotes"); + popOut.classList.add("fa", "fa-external-link"); + summary.append(popOut); + } + + // Create controls for a speaker note window. + function setupSpeakerNotes() { + // Show the notes inline again when the window is closed. + window.addEventListener("pagehide", (event) => { + setState("inline-open"); + }); + + // Hide sidebar and buttons. + document.querySelector("html").classList.remove("sidebar-visible"); + document.querySelector("html").classList.add("sidebar-hidden"); + document.querySelector(".left-buttons").classList.add("hidden"); + document.querySelector(".right-buttons").classList.add("hidden"); + + // Hide content except for the speaker notes and h1 elements. + const main = document.querySelector("main"); + let children = main.childNodes; + let i = 0; + while (i < children.length) { + const node = children[i]; + switch (node.tagName) { + case "DETAILS": + // We found the speaker notes: extract their content. + let div = document.createElement("div"); + div.replaceChildren(...node.childNodes); + node.replaceWith(div); + i += 1; + break; + case "H1": + // We found a header: turn it into a smaller header for the speaker + // note window. + let h4 = document.createElement("h4"); + let pageLocation = new URL(window.location.href); + pageLocation.hash = ""; + let a = document.createElement("a"); + a.setAttribute("href", pageLocation.href); + a.append(node.innerText); + h4.append("Speaker Notes for ", a); + node.replaceWith(h4); + i += 1; + break; + default: + // We found something else: remove it. + main.removeChild(node); + } + } + + // Update prev/next buttons to keep speaker note state. + document.querySelectorAll('a[rel="prev"], a[rel="next"]').forEach((elem) => { + elem.href += "#speaker-notes-open"; + }); + } + + let timeout = null; + // This will fire on _other_ open windows when we change window.localStorage. + window.addEventListener("storage", (event) => { + switch (event.key) { + case "currentPage": + if (getState() == "popup") { + // We link all windows when we are showing speaker notes. + window.location.pathname = event.newValue; + } + break; + case "speakerNotes": + // When nagigating to another page, we see two state changes in rapid + // succession: + // + // - "popup" -> "inline-open" + // - "inline-open" -> "popup" + // + // When the page is closed, we only see: + // + // - "popup" -> "inline-open" + // + // We can use a timeout to detect the difference. The effect is that + // showing the speaker notes is delayed by 500 ms when closing the + // speaker notes window. + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(applyState, 500); + break; + } + }); + window.localStorage["currentPage"] = window.location.pathname; + + // We encode the kind of page in the location hash: + switch (window.location.hash) { + case "#speaker-notes-open": + // We are on a page in the speaker notes. We need to re-establish the + // popup state so that the main window will hide the notes. + setState("popup"); + setupSpeakerNotes(); + break; + case "#speaker-notes-defunct": + // We are on a page in a defunct speaker note window. We keep the state + // unchanged and mark the window defunct. + setupSpeakerNotes(); + markDefunct(); + break; + default: + // We are on a regular page. We force the state to "inline-open" if this + // looks like a direct link to the speaker notes. + if (window.location.hash == "#speaker-notes") { + setState("inline-open"); + } + applyState(); + setupRegularPage(); + } +})(); diff --git a/src/hello-world.md b/src/hello-world.md index 90988d4610c1..f030d57a40d7 100644 --- a/src/hello-world.md +++ b/src/hello-world.md @@ -16,3 +16,21 @@ What you see: * The `main` function is the entry point of the program. * Rust has hygienic macros, `println!` is an example of this. * Rust strings are UTF-8 encoded and can contain any Unicode character. + +
+ +This slide tries to make the students comfortable with Rust code. They will see +a ton of it over the next four days so we start small with something familiar. + +Key points: + +* Rust is very much like other languages in the C/C++/Java tradition. It is + imperative (not functional) and it doesn't try to reinvent things unless + absolutely necessary. + +* Rust is modern with full support for things like Unicode. + +* Rust uses macros for situations where you want to have a variable number of + arguments (no function [overloading](basic-syntax/functions-interlude.md)). + +
diff --git a/src/hello-world/small-example.md b/src/hello-world/small-example.md index b1df4b1d2ed6..3a0ba8d35113 100644 --- a/src/hello-world/small-example.md +++ b/src/hello-world/small-example.md @@ -18,3 +18,27 @@ fn main() { // Program entry point } ``` +
+ +The code implements the Collatz conjecture: it is believed that the loop will +always end, but this is not yet proved. Edit the code and play with different +inputs. + +Key points: + +* Explain that all variables are statically typed. Try removing `i32` to trigger + type inference. Try with `i8` instead and trigger a runtime integer overflow. + +* Change `let mut x` to `let x`, discuss the compiler error. + +* Show how `print!` gives a compilation error if the arguments don't match the + format string. + +* Show how you need to use `{}` as a placeholder if you want to print an + expression which is more complex than just a single variable. + +* Show the students the standard library, show them how to search for `std::fmt` + which has the rules of the formatting mini-language. It's important that the + students become familiar with searching in the standard library. + +
diff --git a/src/welcome-day-1.md b/src/welcome-day-1.md index a6f4fb0cd4fa..4817d6d3e6ae 100644 --- a/src/welcome-day-1.md +++ b/src/welcome-day-1.md @@ -10,3 +10,20 @@ today: management, and garbage collection. * Ownership: move semantics, copying and cloning, borrowing, and lifetimes. + +
+ +The idea for the first day is to show _just enough_ of Rust to be able to speak +about the famous borrow checker. The way Rust handles memory is a major feature +and we should show students this right away. + +If you're teaching this in a classroom, this is a good place to go over the +schedule. We suggest splitting the day into two parts (following the slides): + +* Morning: 9:00 to 12:00, +* Afternoon: 13:00 to 16:00. + +You can of course adjust this as necessary. Please make sure to include breaks, +we recommend a break every hour! + +
diff --git a/src/welcome-day-1/what-is-rust.md b/src/welcome-day-1/what-is-rust.md index e81389a1e2f7..ed226ad2df00 100644 --- a/src/welcome-day-1/what-is-rust.md +++ b/src/welcome-day-1/what-is-rust.md @@ -14,3 +14,14 @@ Rust is a new programming language which had its 1.0 release in 2015: * mobile phones, * desktops, * servers. + + +
+ +Rust fits in the same area as C++: + +* High flexibility. +* High level of control. +* Can be scaled down to very constrained devices like mobile phones. + +