Chapter 2: Language foundations
Introduces the chapter’s plan for learning Rust fundamentals through examples and a grep-lite project.
After this chapter you will be able to
- Source code to running program. Explain that a plain text file containing the right programming-language symbols can be converted into something a CPU can interpret.
- Single-file compilation steps. Describe how to compile a single Rust source file: save code containing a main() function, open a shell, and run rustc with the file name.
- Cargo and rustc roles. Explain why the section switches from rustc to cargo and how the two tools relate.
- Single-file project migration. Describe how to move an existing Rust source file into a cargo project, then run it and locate its executable.
- Familiar Rust syntax conventions. Describe how Rust uses familiar programming elements and identify its basic conventions for blocks, assignment, and whitespace.
- Variable bindings and numeric types. Explain how the example declares variables and shows different ways to specify an integer type.
- Function calls and return syntax. Describe how the example calls add and declares its parameters and return type, including the effect of an extra semicolon.
- Numeric literals and operators. Describe how Rust writes integer and floating-point literals and uses familiar infix operators in numeric expressions. Explain that the same operator token can work with multiple types.
- Numeric types and conversions. Explain that Rust has many numeric types whose size affects their range and ability to represent negative numbers, and that conversions between types must be explicit.
- Nondecimal integer notation. Explain how Rust writes binary, octal, and hexadecimal integer literals and how their place values determine the number they represent.
- Integer width tradeoff. Explain what integer width measures and why choosing a wider type affects both representable range and storage.
- Comparing different numeric types. Explain why Rust rejects a comparison between different numeric types and how a cast can make the operands comparable. Describe why casting a smaller type to a larger one is generally safer.
- Floating-point equality hazards. Explain why direct equality tests on floating-point calculations can fail even when the corresponding decimal mathematics appears equal. Describe the section’s suggested alternative of checking whether a result falls within an acceptable margin.
- NaN and undefined results. Describe how floating-point values handle mathematically undefined results and why the section recommends `is_nan()` and `is_finite()` checks.
- Specialized numeric types. Explain why a Rust program might use the num crate and how to add it to the example project.
- Complex value initialization. Distinguish the two ways the example initializes a Complex value and describe how its fields are accessed.
- Purpose of flow control. Explain why a program uses flow control instead of always executing from top to bottom.
- Direct collection iteration. Explain that a `for` loop makes each successive element of a collection available as the loop item.
- Collection reuse through references. Explain why the section uses a reference when a collection must be reused after a `for` loop, and distinguish that from using a mutable reference to modify items.
- Direct iteration versus indexing. Justify the section’s preference for direct collection iteration over manually incrementing an index, while noting that indexing is sometimes necessary.
- While loop condition. Explain that a while loop keeps executing its block as long as its condition evaluates to true, and stops when that condition no longer holds. Describe the condition as a predicate that can be any expression evaluating to true or false.
- Unconditional loop behavior. Explain that `loop` repeatedly executes a code block and does not stop on its own; it continues until a `break` is encountered or the program is terminated from outside.
- Labeled nested loop exit. Describe how a loop label lets break exit a nested loop, and why the section presents labels as useful in place of goto for jumping to cleanup after an error.
- Boolean conditional branching. Explain what an if condition accepts, how an if else chain handles multiple tests, and what the final else covers.
- Values from Rust expressions. Explain what the section means by calling Rust expression-based, using its examples of functions, conditional expressions, match, and break.
- Exhaustive match branches. Explain why match is a safer alternative to if/else for handling multiple possibilities: the compiler rejects a match that leaves a possible value unhandled.
- Function parameter and return types. Explain that a Rust function definition specifies the type of each parameter and the function’s return type, using add as an example.
- Reference meaning and operators. Explain that a reference stores another value’s memory address rather than duplicating that value, and describe how & creates a reference and * dereferences it to access the value.
- Mandelbrot project setup and run. Describe how to create the Mandelbrot project, add its dependency, replace its source file, and run it to see the terminal rendering.
- Lifetime Signature Notation. Describe how `<'a, 'b>` declares lifetime variables and how `i: &'a i32` and `j: &'b i32` associate them with reference parameters.
- Lifetime Safety And Inference. Explain that lifetime annotations express the programmer’s intent for Rust’s checks on valid data access, while the compiler usually infers lifetimes and may ask for help in harder cases.
- Generic type placeholders. Explain how a generic type variable lets one function accept multiple input types while requiring uses of the same type variable to match.
- Addition trait bounds. Explain why a generic function cannot add arbitrary T values and how the bound <T: std::ops::Add<Output = T>> makes that operation valid.
- Line by line text search. Describe how grep-lite searches text by iterating over lines and checking each line for a pattern.
- String and string slice distinctions. Distinguish String from &str in terms of changing text, ownership, and how a string slice refers to text.
- Enumerated matching lines. Describe how grep-lite can print line numbers with matches and how enumerate() replaces manual index increments.
- Array and vector tradeoffs. Distinguish arrays from vectors by explaining that arrays have a fixed width and are lightweight, while vectors can grow but have a small runtime cost from extra bookkeeping.
- Array structure and type. Explain that a Rust array holds elements of one type in contiguous memory, has a fixed length, and includes that length in its type.
- Array indexing bounds. Explain what Rust does when array indexing requests an element outside the array's bounds.
- Slice size and type. Distinguish a slice from an array by explaining what dynamically sized means here and how their type signatures differ.
- Slice views without copying. Explain how a slice can serve as a view on an array or another slice.
- Referenced slice representation. Explain why slices are typically seen as &[T] and describe the two components given for that referenced form.
- Vector flexibility and cost. Explain how a Vec<T> differs from an array and why its flexibility comes with a small runtime cost.
- Two-pass context collection. Trace the two-pass strategy for finding lines of context around grep-lite matches.
- Third-party crates and regex. Explain why third-party crates are common in Rust projects and what the regex crate adds.
- Cargo dependency setup. Describe how the section creates a grep-lite project, adds regex version 1 as a dependency, and builds it.
- Regular expression search flexibility. Explain why regular expressions are added to grep-lite and describe the limits of what this example demonstrates.
- Local crate documentation. Describe how to generate and open local documentation for a Rust project’s dependencies, and why a local copy can be useful.
- Rustup and Cargo roles. Distinguish rustup’s management of Rust installations and toolchains from cargo’s management of projects, and explain why switching compiler versions is useful.
- User-selected search patterns. Explain why grep-lite adds clap and what it enables users to choose.
- Arguments passed through Cargo. Describe how to pass command-line arguments to grep-lite when running it with cargo run, and what happens when insufficient arguments are passed.
- Buffered file line reading. Describe the pattern of opening a File and wrapping it in a BufReader, then explain why Rust’s line iterator is convenient for reading lines.
- Shared file and stdin handling. Explain why the utility uses a generic function when supporting both files and stdin.
Section by section
Introduction
Introduces the chapter’s plan for learning Rust fundamentals through examples and a grep-lite project.
2.1 Creating a running program
Introduces the idea that Rust source code is text that can be converted into a program a CPU can interpret.
- Source code to running program: Explain that a plain text file containing the right programming-language symbols can be converted into something a CPU can interpret.
Why it matters: This is the basic connection between writing Rust code and running a program.
Key terms: plain text file, Rust source code, CPU, running program
2.1.1 Compiling single files with rustc
This section explains how rustc turns a single Rust source file into an executable and shows how to compile one from a shell.
- Single-file compilation steps: Describe how to compile a single Rust source file: save code containing a main() function, open a shell, and run rustc with the file name.
Why it matters: These steps let you turn a standalone Rust file into a runnable program. - Compiler role and output: Explain what rustc does with a Rust source file and what to expect when compilation succeeds.
Why it matters: Knowing that silence can mean success helps you recognize that rustc has created an executable.
Key terms: rustc, main(), source code, shell, compiler, machine code, executable
2.1.2 Compiling Rust projects with cargo
This section shows how to move a single-file Rust program into a cargo project and run it with cargo.
- Cargo and rustc roles: Explain why the section switches from rustc to cargo and how the two tools relate.
Why it matters: This explains why the workflow changes as a Rust program becomes a project. - Single-file project migration: Describe how to move an existing Rust source file into a cargo project, then run it and locate its executable.
Why it matters: These steps let you keep working with an existing program using cargo rather than direct rustc commands.
Key terms: cargo, rustc, dependencies, cargo init, cargo run, target, verbose flag (-v)
2.2 A glance at Rust’s syntax
The section presents Rust’s syntax as largely familiar and predictable, with a few basic conventions.
- Familiar Rust syntax conventions: Describe how Rust uses familiar programming elements and identify its basic conventions for blocks, assignment, and whitespace.
Why it matters: These conventions help you recognize the structure of the Rust examples that follow.
Key terms: curly brackets, assignment operator, whitespace-agnostic
2.2.1 Defining variables and calling functions
This section uses a short Rust program to introduce variable bindings, type annotations, function syntax, and formatted console output.
- Program entry point and scopes: Describe the role of main() and how Rust marks a function definition and its code block.
Why it matters: These are the basic landmarks for reading the example program. - Variable bindings and numeric types: Explain how the example declares variables and shows different ways to specify an integer type.
Why it matters: It lets you read the example’s declarations without mistaking alternative integer spellings for different operations. - Function calls and return syntax: Describe how the example calls add and declares its parameters and return type, including the effect of an extra semicolon.
Why it matters: These details explain both the nested calculation and why punctuation can change what add returns. - Console output and text syntax: Explain what println! does in the example and how its format string represents text and inserts a value.
Why it matters: It explains how the example turns its computed result into the line printed on the console.
Key terms: fn, main(), code blocks, lexical scopes, let, variable bindings, immutable, i32, numeric literals, type annotations
2.3 Numbers
Previews a discussion of creating numeric types and performing operations on them in Rust.
2.3.1 Integers and decimal (floating-point) numbers
Introduces Rust’s basic numeric syntax, its distinct numeric types and explicit conversions, and methods on numbers.
- Numeric literals and operators: Describe how Rust writes integer and floating-point literals and uses familiar infix operators in numeric expressions. Explain that the same operator token can work with multiple types.
Why it matters: This gives you a starting point for reading Rust’s numeric expressions. - Numeric types and conversions: Explain that Rust has many numeric types whose size affects their range and ability to represent negative numbers, and that conversions between types must be explicit.
Why it matters: The chosen numeric type affects which values an expression can hold and whether a conversion is needed. - Methods on numeric values: Describe how a number can have a method, using the section’s rounding example, and why that example includes a type suffix.
Why it matters: It helps you recognize method-call syntax when working with numbers.
Key terms: integer, floating-point numbers, infix notation, operator overloading, numeric types, size in bytes, explicit conversions, methods, type suffix, concrete type
2.3.2 Integers with base 2, base 8, and base 16 notation
The section introduces Rust’s binary, octal, and hexadecimal integer notation, then explains numeric type families and how integer width affects range and storage.
- Nondecimal integer notation: Explain how Rust writes binary, octal, and hexadecimal integer literals and how their place values determine the number they represent.
Why it matters: You can read integer values even when Rust code expresses them in a base other than decimal. - Numeric type families: Distinguish the signed integer, unsigned integer, and floating-point families described in the section.
Why it matters: These distinctions help you interpret what a Rust numeric type can represent. - Integer width tradeoff: Explain what integer width measures and why choosing a wider type affects both representable range and storage.
Why it matters: Width connects a type’s numeric range to its cost in storage.
Key terms: numeric literals, binary, octal, hexadecimal, 0b, 0o, signed integers, unsigned integers, floating-point types, i
2.3.3 Comparing numbers
This section explains how Rust compares numeric values and why type conversions, floating-point approximation, and NaN require care.
- Comparing different numeric types: Explain why Rust rejects a comparison between different numeric types and how a cast can make the operands comparable. Describe why casting a smaller type to a larger one is generally safer.
Why it matters: Making types match is necessary for the comparison, but a careless conversion can silently change the number. - Fallible conversion with try_into: Describe how the section uses `try_into()` instead of `as` when a conversion might fail, including the role of importing `TryInto` and handling the returned `Result`.
Why it matters: This makes a potentially unsuccessful conversion explicit rather than treating every cast as harmless. - Floating-point equality hazards: Explain why direct equality tests on floating-point calculations can fail even when the corresponding decimal mathematics appears equal. Describe the section’s suggested alternative of checking whether a result falls within an acceptable margin.
Why it matters: An exact equality check can reject a useful floating-point result simply because of representation error. - NaN and undefined results: Describe how floating-point values handle mathematically undefined results and why the section recommends `is_nan()` and `is_finite()` checks.
Why it matters: Checking for invalid floating-point results helps prevent a mathematical error from silently spreading through later calculations.
Key terms: as, type cast, promotion, demoted, TryInto, try_into(), trait, Result, unwrap(), floating-point
2.3.4 Rational, complex numbers, and other numeric types
This section shows how to use a third-party crate for specialized numeric types and introduces complex numbers and two ways to initialize them.
- Specialized numeric types: Explain why a Rust program might use the num crate and how to add it to the example project.
Why it matters: It connects Rust’s smaller standard library with the practical way this example obtains additional number types. - Complex number parts: Describe how the section represents a complex number and what its two parts mean.
Why it matters: The two parts explain what the fields in the example's Complex values hold. - Complex value initialization: Distinguish the two ways the example initializes a Complex value and describe how its fields are accessed.
Why it matters: These distinctions help a reader recognize both forms of value creation without mistaking new() for special Rust syntax. - Scoped Complex import: Explain what the example's use statement imports from the num crate.
Why it matters: It explains how the example refers to a type supplied by its dependency.
Key terms: standard library, num crate, crate, Cargo.toml, [dependencies], complex numbers, real, imaginary, re, im
2.4 Flow control
Introduces flow control as a way for Rust programs to depart from ordinary top-to-bottom execution.
- Purpose of flow control: Explain why a program uses flow control instead of always executing from top to bottom.
Why it matters: This gives you the basic purpose of the flow control mechanisms introduced next.
Key terms: flow control
2.4.1 For: The central pillar of iteration
This section introduces Rust’s `for` loop, explains how references affect reuse and modification of a collection, and compares direct iteration with manual indexing.
- Direct collection iteration: Explain that a `for` loop makes each successive element of a collection available as the loop item.
Why it matters: Direct iteration is the section’s starting point for choosing how to loop over a collection. - Collection reuse through references: Explain why the section uses a reference when a collection must be reused after a `for` loop, and distinguish that from using a mutable reference to modify items.
Why it matters: The choice of loop form affects whether the collection can be used afterward or its items can be changed. - Fixed-count anonymous loops: Describe how an underscore and a range express a loop that runs a fixed number of times without using the loop variable.
Why it matters: This form makes it clear when repetition, rather than the value of each item, is the point of a loop. - Direct iteration versus indexing: Justify the section’s preference for direct collection iteration over manually incrementing an index, while noting that indexing is sometimes necessary.
Why it matters: It explains both the performance and safety reasons for the section’s preferred loop style.
Key terms: for loop, container, item, reference, ampersand (&), mutable reference, mut, anonymous loops, underscore (_), exclusive range
2.4.2 Continue: Skipping the rest of the current iteration
2.4.3 While: Looping until a condition changes its state
This subsection explains how a while loop repeats while a Boolean condition holds and notes that Rust programmers prefer loop for endless repetition.
- While loop condition: Explain that a while loop keeps executing its block as long as its condition evaluates to true, and stops when that condition no longer holds. Describe the condition as a predicate that can be any expression evaluating to true or false.
Why it matters: This lets you describe precisely when a while loop continues and when it stops. - Endless loop preference: State that Rust programmers generally prefer the loop keyword over a while idiom when they intend to loop forever.
Why it matters: It distinguishes condition-controlled repetition from the preferred way to express an endless loop.
Key terms: while loop, condition, predicate, while, loop keyword
2.4.4 Loop: The basis for Rust’s looping constructs
Introduces `loop` as a way to repeat a code block until a `break` is encountered or the program is terminated externally.
- Unconditional loop behavior: Explain that `loop` repeatedly executes a code block and does not stop on its own; it continues until a `break` is encountered or the program is terminated from outside.
Why it matters: This explains why `loop` suits work intended to keep running, such as a long-running server.
Key terms: loop, break
2.4.5 Break: Aborting a loop
This subsection explains how break exits a loop and how loop labels let it exit a selected nested loop.
- Basic loop exit: Explain what break does when used in a loop.
Why it matters: It gives you a way to stop looping rather than wait for another condition. - Labeled nested loop exit: Describe how a loop label lets break exit a nested loop, and why the section presents labels as useful in place of goto for jumping to cleanup after an error.
Why it matters: Labels give nested loops a controlled way to exit, including when an error calls for cleanup.
Key terms: break, loop labels, nested loop, goto
2.4.6 If, if else, and else: Conditional branching
This section explains Boolean conditional branching in Rust and shows how conditionals, loops, and other constructs fit its expression-based design.
- Boolean conditional branching: Explain what an if condition accepts, how an if else chain handles multiple tests, and what the final else covers.
Why it matters: It helps you describe which conditions Rust accepts and how its conditional branches are chosen. - Values from Rust expressions: Explain what the section means by calling Rust expression-based, using its examples of functions, conditional expressions, match, and break.
Why it matters: It explains why Rust control-flow constructs can supply values rather than merely direct execution. - Statements and unit value: Distinguish the statements identified here from expressions, and state how Rust represents no value.
Why it matters: This qualifies the claim that almost everything in Rust is an expression.
Key terms: if, if else, else, Boolean, truthy, falsey, expression, return, conditional expressions, match
2.4.7 Match: Type-aware pattern matching
This subsection introduces Rust’s match as a concise way to test multiple values while requiring every possible case to be handled.
- Exhaustive match branches: Explain why match is a safer alternative to if/else for handling multiple possibilities: the compiler rejects a match that leaves a possible value unhandled.
Why it matters: Exhaustive matching helps catch forgotten cases before a program runs. - Match patterns and branch behavior: Describe the patterns shown for testing values in match—an inclusive range, OR, and the underscore—and explain that a matched branch does not fall through to the next option.
Why it matters: These patterns make multiple cases concise, while the branch behavior makes the result predictable.
Key terms: match, compiler error, inclusive ranges, Boolean OR, underscore, fall through
2.5 Defining functions
The section uses a small add function to introduce Rust’s requirement to specify parameter and return types in a function definition.
- Function parameter and return types: Explain that a Rust function definition specifies the type of each parameter and the function’s return type, using add as an example.
Why it matters: These type specifications are foundational to reading and defining Rust functions.
Key terms: parameter’s types, return type, i32
2.6 Using references
This section explains how Rust references stand in for values and how dereferencing works in an array-search example.
- Reference meaning and operators: Explain that a reference stores another value’s memory address rather than duplicating that value, and describe how & creates a reference and * dereferences it to access the value.
Why it matters: This gives you a way to picture what Rust’s reference symbols do without imagining that the original value is copied. - Array search reference traversal: Trace how item refers to successive elements of haystack in the array-search example and how dereferencing item obtains each element’s value.
Why it matters: The example connects the reference operators to what happens as a program examines an array.
Key terms: reference, reference operator (&), dereference operator (*), item, haystack, *item
2.7 Project: Rendering the Mandelbrot set
This section walks the reader through creating and running a Rust project that renders the Mandelbrot set in the terminal.
- Mandelbrot project setup and run: Describe how to create the Mandelbrot project, add its dependency, replace its source file, and run it to see the terminal rendering.
Why it matters: It turns the Rust basics covered so far into a program you can run and see.
Key terms: Cargo, num crate, Cargo.toml, src/main.rs, cargo run, Mandelbrot set
2.8 Advanced function definitions
This introduction notes that Rust function definitions can be more complex than the earlier example and points readers to the following sections.
2.8.1 Explicit lifetime annotations
This section explains how to read explicit lifetime annotations on reference parameters, why Rust sometimes needs them, and why they appear in function definitions rather than calls.
- Lifetime Signature Notation: Describe how `<'a, 'b>` declares lifetime variables and how `i: &'a i32` and `j: &'b i32` associate them with reference parameters.
Why it matters: Reading this notation makes reference-taking function signatures less mysterious. - Lifetime Safety And Inference: Explain that lifetime annotations express the programmer’s intent for Rust’s checks on valid data access, while the compiler usually infers lifetimes and may ask for help in harder cases.
Why it matters: It explains why lifetime markings can matter even though most Rust code leaves them out. - Separate Parameter Lifetimes: Explain what using separate lifetimes for `i` and `j` communicates, and state where the example requires the annotations.
Why it matters: It helps you interpret what the two names communicate without looking for lifetime syntax at every call.
Key terms: lifetime variables, lifetime annotations, reference, function signature, lifetime system, lifetime elision, lifetime parameters, decoupled, function definition
2.8.2 Generic functions
This section explains how generic functions accept multiple types and how trait bounds restrict those types when a function uses an operator.
- Generic type placeholders: Explain how a generic type variable lets one function accept multiple input types while requiring uses of the same type variable to match.
Why it matters: This is how one strongly typed function can be reused with different types. - Addition trait bounds: Explain why a generic function cannot add arbitrary T values and how the bound <T: std::ops::Add<Output = T>> makes that operation valid.
Why it matters: A trait bound states the capability a generic function needs rather than assuming every type has it. - Traits behind operators: Describe a trait as a way for types to advertise common behavior, and explain how Rust expresses operators through traits.
Why it matters: It explains why an operator in a generic function calls for a trait bound.
Key terms: generic type, type variable, T, trait bound, std::ops::Add, Output = T, trait, operator overloading, syntactic sugar
2.9 Creating grep-lite
This section builds a simple text search example while introducing Rust’s string types and iterator-based line numbering.
- Line by line text search: Describe how grep-lite searches text by iterating over lines and checking each line for a pattern.
Why it matters: These two string methods provide the basic search behavior that grep-lite builds on. - String and string slice distinctions: Distinguish String from &str in terms of changing text, ownership, and how a string slice refers to text.
Why it matters: The distinction helps explain why seemingly similar text values can require different methods and produce type errors. - String literal lifetime: Explain the type of a string literal and the section’s reason for its 'static lifetime.
Why it matters: It explains why a quoted piece of text is a string slice rather than an owned String. - Enumerated matching lines: Describe how grep-lite can print line numbers with matches and how enumerate() replaces manual index increments.
Why it matters: enumerate() supplies the position needed for line-numbered output without a separate manual counter.
Key terms: grep-lite, lines(), contains(), String, str, &str, string slice, owned, borrowed, string literal
2.10 Making lists of things with arrays, slices, and vectors
Introduces the basic tradeoff between arrays and vectors before examining Rust’s list types in more detail.
- Array and vector tradeoffs: Distinguish arrays from vectors by explaining that arrays have a fixed width and are lightweight, while vectors can grow but have a small runtime cost from extra bookkeeping.
Why it matters: This tradeoff helps explain why the section considers more than one way to hold a list of things.
Key terms: arrays, vectors, fixed-width, growable, bookkeeping
2.10.1 Arrays
This section explains how Rust arrays are created, how their fixed length forms part of their type, and what happens when indexing goes out of bounds.
- Array structure and type: Explain that a Rust array holds elements of one type in contiguous memory, has a fixed length, and includes that length in its type.
Why it matters: The fixed length explains both array type notation and how arrays differ from the collection types introduced next. - Array creation forms: Describe the two array-creation forms shown here: listing elements in square brackets and repeating a value a specified number of times.
Why it matters: These two forms let you recognize or create arrays from explicit elements or a repeated value. - Array indexing bounds: Explain what Rust does when array indexing requests an element outside the array's bounds.
Why it matters: Bounds checking is a concrete example of Rust prioritizing safety over silently returning bad data.
Key terms: array, [T; n], element type, repeat expression, array indexing, bounds checked, panics
2.10.2 Slices
This section explains how slices differ from arrays, why they are useful, and why they are usually used through references.
- Slice size and type: Distinguish a slice from an array by explaining what dynamically sized means here and how their type signatures differ.
Why it matters: It prevents “dynamically sized” from being mistaken for an array that grows and shrinks. - Slice views without copying: Explain how a slice can serve as a view on an array or another slice.
Why it matters: A slice can expose existing data without making a copy. - Referenced slice representation: Explain why slices are typically seen as &[T] and describe the two components given for that referenced form.
Why it matters: It explains both the common &[T] notation and how a reference can carry a slice’s length. - Traits across array sizes: Explain why implementing traits for slices can be easier than implementing them for arrays of different sizes.
Why it matters: Slices offer a size-independent way to work with arrays when adding behavior through traits.
Key terms: slice, dynamically sized, array, [T], [T; n], view, reference, &[T], usize, pointer
2.10.3 Vectors
Introduces growable vectors through a grep-lite context-lines example, explaining their tradeoffs, nested types, and a text-encoding caution.
- Vector flexibility and cost: Explain how a Vec<T> differs from an array and why its flexibility comes with a small runtime cost.
Why it matters: This helps you choose a growable collection when the number of items may change. - Two-pass context collection: Trace the two-pass strategy for finding lines of context around grep-lite matches.
Why it matters: Separating match detection from context collection makes the example easier to follow. - Nested vector line records: Describe what Vec<Vec<(usize, String)>> contains in the example.
Why it matters: Reading the nested type makes the context-lines example less intimidating. - Capacity hint and allocation: Explain why providing an estimated size with Vec::with_capacity() can improve vector performance.
Why it matters: An estimate can reduce allocations when you expect a vector to grow. - UTF-8 input limitation: Explain why reading arbitrary text-file bytes directly into a String can fail and what the section suggests instead.
Why it matters: The distinction matters when grep-lite encounters files whose bytes are not valid UTF-8.
Key terms: Vec<T>, vectors, arrays, two-pass strategy, grep-lite, context, tags, Vec<Vec<(usize, String)>>, tuple, usize
2.11 Including third-party code
Introduces why Rust projects use third-party crates and shows how to add and build the regex crate with Cargo.
- Third-party crates and regex: Explain why third-party crates are common in Rust projects and what the regex crate adds.
Why it matters: This explains why grep-lite needs an external dependency to support regular-expression searches. - Cargo dependency setup: Describe how the section creates a grep-lite project, adds regex version 1 as a dependency, and builds it.
Why it matters: These commands make third-party code available to the project before it is used.
Key terms: third-party crates, standard library, regex crate, regular expressions, exact matches, cargo new, cargo add regex@1, dependency, Cargo.toml, cargo build
2.11.1 Adding support for regular expressions
This section adds regular-expression support to grep-lite and shows how to run the updated example, while noting that the example uses little of the new flexibility.
- Regular expression search flexibility: Explain why regular expressions are added to grep-lite and describe the limits of what this example demonstrates.
Why it matters: This distinguishes the reason for adding regex support from what the example actually demonstrates. - Regex-enabled grep-lite run: Describe the steps given for preparing and running the regex-enabled grep-lite example.
Why it matters: These steps connect the added dependency and source code to a runnable example.
Key terms: regular expressions, patterns, grep-lite, grep-lite/Cargo.toml, regex, grep-lite/src/main.rs, cargo run
2.11.2 Generating the third-party crate documentation locally
This section shows how to generate and view local HTML documentation for a project’s third-party crates.
- Local crate documentation: Describe how to generate and open local documentation for a Rust project’s dependencies, and why a local copy can be useful.
Why it matters: You can consult dependency documentation even when the online version is unavailable.
Key terms: third-party crates, cargo doc, HTML documentation, cargo doc --open
2.11.3 Managing Rust toolchains with rustup
This section explains how rustup manages Rust toolchains and opens local Rust documentation.
- Rustup and Cargo roles: Distinguish rustup’s management of Rust installations and toolchains from cargo’s management of projects, and explain why switching compiler versions is useful.
Why it matters: Keeping project management and toolchain management distinct helps you choose the right tool. - Local documentation access: Describe what happens when you run rustup doc.
Why it matters: It gives you a quick way to consult standard-library documentation locally.
Key terms: rustup, cargo, Rust toolchains, nightly, stable, rustup doc, standard library
2.12 Supporting command-line arguments
The section adds command-line argument support to grep-lite with clap and shows how to pass arguments through cargo run.
- User-selected search patterns: Explain why grep-lite adds clap and what it enables users to choose.
Why it matters: A user-supplied pattern makes grep-lite more useful as a command-line utility. - Arguments passed through Cargo: Describe how to pass command-line arguments to grep-lite when running it with cargo run, and what happens when insufficient arguments are passed.
Why it matters: The separator lets you test grep-lite’s new command-line input through Cargo. - Generated usage documentation: State what clap provides beyond parsing command-line arguments.
Why it matters: Generated help makes the utility’s command-line usage discoverable.
Key terms: grep-lite, clap, command-line arguments, pattern, cargo run, --, executable binary, usage documentation, --help
2.13 Reading from files
This section introduces reading a file through a buffered reader and using a line iterator instead of manually looping through the file.
- Buffered file line reading: Describe the pattern of opening a File and wrapping it in a BufReader, then explain why Rust’s line iterator is convenient for reading lines.
Why it matters: This gives you a practical way to read input files line by line without writing the loop manually.
Key terms: File, BufReader, buffered I/O, helper iterator
2.14 Reading from stdin
The section introduces a generic function as a way for a command-line utility to read from either a file or stdin without duplicating code in main().
- Shared file and stdin handling: Explain why the utility uses a generic function when supporting both files and stdin.
Why it matters: This explains why adding stdin support need not create a second copy of the input-handling code.
Key terms: stdin, generic function, main()
2.15 Summary
Where this is used later
- Setting up intrinsics in a program (chapter 12) · The earlier introduction to compiling Rust files with rustc.
- Life of an integer (chapter 5) · The earlier introduction to integer types.
- Bit patterns and types (chapter 5) · Develops the relationship between types and bit patterns.
- Implementing a hexdump clone (chapter 7) · hexadecimal notation
- Making use of the Result return type (chapter 3) · The section’s first encounter with `Result` supports its later treatment as a return type.
- Floating-point numbers (chapter 5) · The deeper examination of floating-point numbers builds on their approximate representation.
- Simplifying object creation by implementing new() (chapter 3) · It introduces the new() convention before that section implements one for a struct.
- Using plain functions to experiment with an API (chapter 3) · The reader has encountered loop as a looping construct.
Concepts explained in this chapter
- slicingA slice is a dynamically sized, fixed-length view of contiguous elements that does not own or resize the underlying storage.
- traitThe trait keyword is Rust's reserved keyword for declaring a trait.
- floating-point arithmeticA floating-point type represents real-number values using a finite-precision binary format with sign, exponent, and significand components.
- vectorA vector is a dynamically resizable, ordered collection of elements.
- literalA literal form is Rust syntax that directly initializes or denotes a value according to the type's literal representation.
- data conversionA type cast is an explicit conversion of a value from one data type to another.
- TryIntoTryInto is a Rust standard-library trait for fallible conversion of a value into a specified target type.
- as operatorThe as operator is Rust syntax for explicitly converting a value to another type using a permitted cast.
- epsFloating-point epsilon is a small tolerance used to assess whether a computed value is sufficiently close to an expected mathematical result.
- exceptionAn exception is a synchronous or otherwise special execution event that interrupts normal program flow because of an error or exceptional condition.
- nanNot a number is a floating-point special value representing an undefined or unrepresentable numeric result.
- result typeResult is Rust's standard result type that contains either a successful value or an error value.
- type safetyType safety is the enforcement of rules that prevent values of incompatible types from being used together in invalid operations.
- cargo addcargo add is a Cargo subcommand that adds a dependency declaration to a Rust project's manifest.
- stringString is Rust's owned, growable UTF-8 string type whose character data is stored in heap-allocated memory.
- unit typeThe unit type is a type with exactly one value, written () in Rust, and represents the absence of a meaningful value.
- num crateThe num crate is a Rust library providing numeric types and operations beyond Rust's built-in numeric types.
- loopA for loop repeatedly executes a block while iterating over the elements of a sequence or iterator.
- referenceA reference is a non-owning value that provides access to another value without transferring its ownership.
- static methodA static method is a function associated with a type rather than with an instance of that type.
- let keywordA let binding introduces a local Rust variable binding whose associated value may have internally mutable state even when the binding itself is immutable.
- CargoCargo is Rust's package manager and build system for creating, compiling, running, documenting, and managing Rust projects.
- integerAn integer is a whole-number value without a fractional component.
- lifetimeA lifetime is the statically determined span during which a Rust value or reference remains valid.
- arrayAn array is a contiguous, indexable sequence of a fixed number of elements whose size is part of its type.
- expression-based languageAn expression-based language treats expressions as constructs that produce values and can generally be composed wherever values are expected.
- hexadecimal numberHexadecimal notation is a base-16 positional numeral system that represents values using sixteen distinct digits.
- dereferencingDereferencing is the operation of accessing the value designated by a pointer or reference.
- dependencyA dependency is an external software package required by a project.
- compilerA compiler is a program that translates source code into executable or lower-level code while checking it against the language's rules.
- cargo runcargo run is a Cargo subcommand that builds a Rust project and executes its resulting binary.
- RustRust is a compiled systems programming language designed for memory safety, concurrency, and performance without relying on garbage collection.
- type hintsA type annotation is source-code syntax that explicitly specifies the type of an expression, variable, or parameter.
- cargo.tomlCargo.toml is a Rust package manifest that declares project metadata and dependencies.
- scopeA scope is a region of program text that determines where names and declarations are visible.
- 64-bit floating-point numberA double-precision floating-point type is a floating-point type represented with the precision associated with Rust's f64 type.
- unwrapThe unwrap method extracts a successful value from a Result or Option and panics when it contains an error or no value.
- ownershipAn owner is the binding or program entity responsible for cleaning up a value when its lifetime ends.
- pipelineA pipeline is a sequence of processing stages in which each stage transforms data for the next stage.
- attributeAn attribute is Rust syntax that attaches metadata to a program item and can control compiler behavior such as conditional inclusion.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
