Skip to content

and, or and a first program's explanation, run

Level: 101 · a companion to Functions and Variables

One line: The two-function and/or program that opens chapter 1 of Rust for Machine Learning compiles and prints what the chapter says it will. Three statements in the explanation around it do not hold on rustc 1.98.0, and six more are true but narrower than they read.

fn and(a: bool, b: bool) -> bool {
    a && b
}

fn or(a: bool, b: bool) -> bool {
    a || b
}

fn main() {
    println!("{}", and(true, false)); // false
    println!("{}", or(true, false)); // true
}

Where it comes from

Rust for Machine Learning by Marcos Silveira, O'Reilly, read as an Early Release (first release 2026-07-23, print listed for August 2027). Chapter 1, "The First Neuron", works toward a perceptron. It first writes the boolean functions AND and OR in Rust, then treats their truth tables as a dataset to learn from. The part checked here is the section "Building Intuition with AND and OR" and the opening of "Replacing Code with Data". An Early Release is the author's unedited draft, so any of this may be fixed before print. The book is listed on the shelf, and the chapter's next section, its first test, is A first test, step by step.

The program in the chapter is the one above, with a and b in variables and a longer format string. Built the way the chapter says, with bare rustc main.rs and then ./main, it prints its two lines exactly. Bare rustc compiles as edition 2015, which makes no difference to a program this small. and and or are ordinary names: neither is a keyword in any edition, unlike in Python and C++ (below).

Three statements that do not hold

1. The last expression is returned "explicitly". It is returned implicitly, and rustc uses that word itself. Put a ; after the last line of a function that declares a return type, and the error's label reads implicitly returns () as its body has no tail or return expression. The full transcript is on Functions. return is the explicit form.

2. The short let snippets compile. As printed, three of the four do not: the mut pair, the shadowing pair and the annotated let have no semicolons. The mut pair is introduced as code that will compile, and it stops in the parser:

Abridged — real rustc output for missing_semicolon.rs, the two warnings dropped
error: expected `;`, found `a`
 --> missing_semicolon.rs:2:21
  |
2 |     let mut a = true
  |                     ^ help: add `;` here
3 |     a = false
  |     - unexpected token

With the semicolons added, each snippet shows what the text says it does, and Variables covers all three. The one snippet meant to fail, let a = true; followed by a = false;, does fail, with E0384 cannot assign twice to immutable variable.

3. Operators are syntactic sugar for trait method calls. This forward reference sits beside && and ||, the two binary operators it is not true of. They short-circuit, and a method call cannot, because its arguments are evaluated before it runs. There is no trait for them to call:

Abridged — real rustc output for ops_and.rs
error[E0432]: unresolved import `std::ops::And`
 --> ops_and.rs:1:5
  |
1 | use std::ops::And;
  |     ^^^^^^^^^^---
  |               |
  |               no `And` in `ops`

For bool, the single-character operators are the ones that go through traits: & is BitAnd, | is BitOr and ! is Not (block 3 below). Operators are traits lists every operator that is a trait and the few that are not.

The claims, run

Verified output of and_or_claims_checked.rs — regenerated by tools/run_examples.py, never hand-typed.

1. The two functions reproduce both truth tables
   A      B      and(A, B)  or(A, B)
   true   true   true       true
   true   false  false      true
   false  true   false      true
   false  false  false      false

2. and() is not the same as &&: a function gets both arguments already evaluated
   noisy(left) && noisy(right):
     evaluated left
     = false
   and(noisy(left), noisy(right)):
     evaluated left
     evaluated right
     = false

3. Operators as trait method calls: true of & | !, not of && ||
   true & false = false  true.bitand(false) = false
   true | false = true   true.bitor(false)  = true
   !true        = false  true.not()         = false
   std::ops has no trait for && or || (use std::ops::And is E0432)

4. An immutable binding may be assigned once, not only at the let
   let a; then a = true; compiles without mut: a = true

5. Shadowing hides a name; the old value is untouched
   first still reads true, the new flag is false

6. A type can come from a later line, not only from the value
   before any push: alloc::vec::Vec<bool>
   after answers.push(true): [true]

7. A function with no -> returns (), and so does main
   nothing_to_return() = ()

8. The ! makes println a macro; the brackets can be any of three kinds
   parentheses: true AND false = false
   brackets:    true OR false = true
   braces:      false AND false = false

Reading the blocks in order. Blocks 2 and 4 to 8 are the six narrower statements.

  1. The tables. and and or reproduce both of the chapter's truth tables, all four rows each. They are the dataset the rest of the chapter learns from.
  2. The two functions only call the operator. They return the same value, but and(x, y) is a call, and a call evaluates every argument first, left to right. && stops when the left side is false. With literals as arguments you cannot see the difference; with an argument that prints, panics or fetches something, you can. The & trap on Meet the bool is the same thing in operator form.
  3. Operators and traits. Statement 3 above, run: for bool, the method forms of &, | and ! print the same values as the operators.
  4. An immutable variable cannot have its value changed. More precisely, a binding without mut may be assigned once. let a; followed by a = true; compiles with no mut, because that assignment is the first. Only a second one is E0384.
  5. The new variable overshadows the value of the old one. Shadowing hides the old name. The old value is untouched: a reference taken before the second let still reads true. A name is not a place and A shadow does not drop take this further.
  6. The type is derived from the value. It can come from a later line instead. Vec::new() says nothing about the element type, yet type_name_of_val prints Vec<bool> on the line before the push that decides it, because inference solves the whole function body at once. Type inference has more cases.
  7. main doesn't return any value. It returns (), like every function with no -> — see the unit type. It may also be declared to return a Result.
  8. println! prints the value inside its parentheses. What makes it a macro is the !, and the brackets can be (), [] or {}. The first argument is not printed as it stands: it is a format string, and it must be a literal (The braces take a name).

Two more statements hold as written, and rustc enforces both. Function and variable names are snake_case: fn predictOne compiles with a non_snake_case warning suggesting predict_one. Parameters need both a name and a type: fn and(a, b) is a parse error whose message mentions @ and |, because a parameter is a pattern. Both transcripts are on Functions.

If you are coming from another language

Python. You could not write this program with these names: and and or are keywords, and def and(a, b): is a SyntaxError on Python 3.14. Python's and and or short-circuit like Rust's, but they return one of their operands, not a bool: 0 and 5 is 0, and [] or "default" is "default". That is what makes x or default a Python idiom. Rust's && and || accept only bool and return only bool. The Rust version of that idiom is a method on Option, such as unwrap_or.

C++. and and or are alternative tokens, spellings of && and || that the language reserves. bool and(bool a, bool b) does not compile. Apple clang 21 stops at the name with expected unqualified-id.

C. Plain C accepts int and(int a, int b), until <iso646.h> is included. That header defines and as a macro for &&, and the same line then fails with expected identifier or '('.

See also

Po polsku

Rozdział 1 książki Rust for Machine Learning (Marcos Silveira, O'Reilly, wersja Early Release z lipca 2026) zaczyna od dwóch funkcji logicznych, and i or, a potem traktuje ich tablice prawdy (truth tables) jak zbiór danych, z którego uczy się perceptron. Sam program kompiluje się i wypisuje dokładnie to, co obiecuje tekst. and i or są w Ruscie zwykłymi nazwami, bo nie są słowami kluczowymi w żadnej edycji — w Pythonie są, a w C++ to alternatywne zapisy && i ||, więc tam taki program by się nie skompilował.

Trzy zdania z objaśnienia nie wytrzymują sprawdzenia na rustc 1.98.0. Po pierwsze, ostatnie wyrażenie jest zwracane niejawnie (implicitly), a nie jawnie — kompilator sam używa słowa implicitly w komunikacie E0308. Po drugie, krótkie fragmenty z let mut, z przesłanianiem (shadowing) i z adnotacją typu nie mają średników, więc w wydrukowanej postaci się nie kompilują. Po trzecie, zapowiedź, że operatory to „cukier składniowy" na wywołania metod cech (traits), nie dotyczy właśnie && i ||: skracają obliczenia (short-circuit), więc żadna cecha ich nie opisuje, a use std::ops::And to E0432. Przez cechy przechodzą &, | i !.

Sześć kolejnych zdań jest prawdziwych, ale węższych, niż brzmią. Funkcja and(x, y) daje tę samą wartość co x && y, ale oblicza oba argumenty przed wywołaniem. Zmienna bez mut może zostać przypisana raz, niekoniecznie w samym let. Przesłonięcie zabiera nazwę, a nie wartość — referencja wzięta wcześniej dalej widzi starą wartość. Typ może wynikać z późniejszej linii, a nie tylko z wartości. main zwraca (). println! jest makrem przez !, a nie przez nawiasy, które mogą być okrągłe, kwadratowe lub klamrowe. Pierwszy argument nie jest wypisywany wprost, bo jest łańcuchem formatującym i musi być literałem.

Szukaj po polsku: tablica prawdy · skracanie obliczeń logicznych · przesłanianie zmiennych · zwrot niejawny · rust implicitly returns () · rust E0384 cannot assign twice · rust std::ops And