Ordinary constructive logic permits three rules that change the shape of that context without inspecting the propositions inside it. These are called structural rules.
Weakening
$$\frac{\Gamma\vdash C}{\Gamma,A\vdash C}\;\mathrm{Weak}$$Add an assumption and never use it.
Contraction
$$\frac{\Gamma,A,A\vdash C}{\Gamma,A\vdash C}\;\mathrm{Contr}$$Treat one assumption as though it supplied two copies.
Exchange
$$\frac{\Gamma,A,B,\Delta\vdash C}{\Gamma,B,A,\Delta\vdash C}\;\mathrm{Exch}$$Reorder assumptions freely.
A substructural logic restricts or removes at least one of these permissions. The context then becomes an inventory of resources: a proof must account for how often and in what order each assumption is allowed to be used.
| System | Weakening? | Contraction? | Exchange? | How an assumption may be used |
|---|---|---|---|---|
| Ordinary intuitionistic logic | Yes | Yes | Yes | Any number of times, in any order |
| Affine logic | Yes | No | Yes | At most once |
| Relevant logic | No | Yes | Yes | At least once |
| Linear logic | No | No | Yes | Exactly once |
| Ordered logic | No | No | No | Exactly once, in order |
Removing contraction does not mean that duplication can never occur. It means duplication is no longer implicit. A system may provide an explicit operation or modality (linear logic uses $!A$) to mark values that are safe to copy or discard.
About the names “affine” and “linear.” The useful connection is algebraic: linear maps preserve addition (no contraction) and $f(0)=0$ (no weakening), while affine maps may include a constant offset.
Weakening:
Consider: “Birds are people, therefore, you are a person.”
If we already know that you are a person, an ordinary proof may use zero copies of the premise “birds are people.” Whether birds are people is irrelevant to your personhood, which is good, because we would like to not get into that long-standing debate here.
A logic without weakening rejects this proof as written: every premise must contribute. The point is not that the conclusion is false, but that this particular premise-to-conclusion argument fails to demonstrate relevance.
Contraction:
You have ten dollars, and a La Prima iced coffee costs six dollars.
With contraction, you can duplicate the premise that you have ten dollars as many times as you wish and therefore buy as many coffees as you'd like.
Without contraction, one copy of your budget premise cannot be reused to buy more than one coffee.
Exchange:
“Take off your socks, take off your shoes; now your feet are bare.”
If actions can be freely reordered, we can quietly repair the instructions so that you take your shoes off before your socks.
Wihout exchange, you cannot reorder the premises and you are stuck either figuring out how to take off your socks before your shoes or you just reject that statemnet.
Identity
Ordinary constructive logic can retrieve $A$ from a larger context because weakening permits unused assumptions. In a strictly linear context, the identity rule accounts for exactly one resource:
Constructive
$$\frac{A\in\Gamma}{\Gamma\vdash A}$$Linear
$$\frac{}{A\vdash A}$$Multiplicative conjunction: tensor
Constructive conjunction
$$\frac{\Gamma\vdash A_1\qquad\Gamma\vdash A_2} {\Gamma\vdash A_1\wedge A_2}\;\wedge I$$ $$\frac{\Gamma\vdash A_1\wedge A_2} {\Gamma\vdash A_1}\;\wedge E_1 \qquad \frac{\Gamma\vdash A_1\wedge A_2} {\Gamma\vdash A_2}\;\wedge E_2$$Linear tensor
$$\frac{\Delta_1\vdash A_1\qquad\Delta_2\vdash A_2} {\Delta_1,\Delta_2\vdash A_1\otimes A_2}\;\otimes I$$ $$\frac{\Delta\vdash A_1\otimes A_2\qquad\Delta',A_1,A_2\vdash C} {\Delta,\Delta'\vdash C}\;\otimes E$$The contexts $\Delta_1$ and $\Delta_2$ must be disjoint partitions of the available resources. The constructive $\wedge I$ rule reuses the same $\Gamma$ in both branches, which would implicitly duplicate every assumption. Also, note that the linear version doesn't have rules for dropping a premise a conjunction because you can't drop premises without using them.
Additive disjunction: plus
Constructive disjunction
$$\frac{\Gamma\vdash A_1}{\Gamma\vdash A_1\vee A_2}\;\vee I_1 \qquad \frac{\Gamma\vdash A_2}{\Gamma\vdash A_1\vee A_2}\;\vee I_2$$ $$\frac{\Gamma\vdash A_1\vee A_2\qquad \Gamma,A_1\vdash B\qquad\Gamma,A_2\vdash B} {\Gamma\vdash B}\;\vee E$$Linear plus
$$\frac{\Delta\vdash A_1}{\Delta\vdash A_1\oplus A_2}\;\oplus I_1 \qquad \frac{\Delta\vdash A_2}{\Delta\vdash A_1\oplus A_2}\;\oplus I_2$$ $$\frac{\Delta\vdash A_1\oplus A_2\qquad \Delta',A_1\vdash B\qquad\Delta',A_2\vdash B} {\Delta,\Delta'\vdash B}\;\oplus E$$A proof chooses a side. Elimination must handle either choice using the same surrounding resources $\Delta'$.
Linear implication: lollipop
Constructive implication
$$\frac{\Gamma,A_1\vdash A_2} {\Gamma\vdash A_1\supset A_2}\;\supset I$$ $$\frac{\Gamma\vdash A_1\supset A_2\qquad\Gamma\vdash A_1} {\Gamma\vdash A_2}\;\supset E$$Linear implication
$$\frac{\Delta,A_1\vdash A_2} {\Delta\vdash A_1\multimap A_2}\;\multimap I$$ $$\frac{\Delta\vdash A_1\multimap A_2\qquad\Delta'\vdash A_1} {\Delta,\Delta'\vdash A_2}\;\multimap E$$Read $A\multimap B$ as a process that consumes one $A$ to produce one $B$. Its symbol $\multimap$ is commonly pronounced “lollipop.”
Rust’s ownership discipline is usually described as affine: an owned value may be consumed at most once, but it may also be dropped unused. Copying resource-owning values is explicit, often through .clone(). Types implementing Copy, such as i32, opt into inexpensive implicit copying.
fn main() {
let x = "hello".to_string();
let y = x;
println!("{x}");
}
Does this compile?
No. Assigning x to y moves the owned String. The later print tries to borrow x after it has been moved. Violates the use at most once property.
fn eat(_: String) {}
fn main() {
let y = "hello".to_string();
eat(y.clone()); // explicit duplication
eat(y); // consume the original
}
Does this compile? What if we call eat(y) once more?
The code shown compiles. clone constructs a second owned string, which may cost time and memory; the following call consumes the original. A third call with y would fail because the original has already moved.
fn print_int(x: i32) { println!("{x}"); }
fn main() {
let z = 5;
print_int(z);
print_int(z);
}
Why does this compile?
i32 implements Copy. Passing z by value copies a small integer rather than moving a uniquely owned resource.
A borrow lends access without transferring ownership. Rust permits any number of shared references &T, or one live mutable reference &mut T, but not both at the same time.
fn print_ref(s: &str) { println!("{s}"); }
fn main() {
let s = "world".to_string();
print_ref(&s);
print_ref(&s);
}
Does this compile?
Yes. Both calls temporarily read the same string; neither call owns or mutates it.
fn modify(s: &mut String) {
s.push_str(" oops");
}
fn main() {
let mut s = "world".to_string();
modify(&mut s);
modify(&mut s);
}
Does this compile?
Yes. They occur sequentially. The first borrow ends before the second begins, so only one mutable reference is active at a time.
fn main() {
let mut s = "world".to_string();
let r1 = &mut s;
let r2 = &mut s;
println!("{r1}");
}
Does this compile?
No. The later use of r1 keeps its exclusive borrow live when r2 is created. Rust refuses two overlapping writers.
fn main() {
let mut foo = 5;
let read_foo = &foo;
let write_foo = &mut foo;
println!("{read_foo}");
}
Does this compile?
No. read_foo remains live until the print, so the intervening exclusive borrow conflicts with it. This is the “many readers or one writer” rule enforced by the borrow checker.
Takeaway. Preventing overlapping mutable access is one of the static guarantees that lets safe Rust rule out data races.
fn dangle() -> &'static str {
let s = String::new();
&s
}
Does this compile?
No. The function owns s, so s is dropped when the function returns. A reference to its contents cannot outlive that allocation, much less satisfy the promised 'static lifetime. Returning an owned String
(s instead of &s as well as changing the return type) would transfer the resource safely.
- Parsing and grammar: words and tokens must be consumed in sequence.
- Protocols and state machines: “authenticate, then send” differs from “send, then authenticate.”
- Computation by rewriting: a transition consumes one configuration and produces the next.
- Session-typed communication: channels follow a prescribed order of sends and receives.
Substructural rules are therefore not only philosophical constraints. They are design tools for types that express ownership, protocols, state changes, and other forms of disciplined resource use that can guarantee correctness and memory safety.
- Frank Pfenning, “Structural Proof Theory”, CMU 15-816 lecture notes. Covers exchange, weakening, contraction, and their relationship to linear logic.
- Frank Pfenning, “Linear Logic”, CMU 15-814 lecture notes. Develops resource interpretations and identifies affine logic as allowing weakening but not contraction.
- The Rust Project, The Rust Programming Language: “What Is Ownership?” Explains moves, clones,
Copy, scope, and automatic dropping. - The Rust Project, The Rust Programming Language: “References and Borrowing” Covers shared and mutable references, dangling references, and borrow-checker rules.
- The Rust Project, The Rust Programming Language: “Fearless Concurrency” Connects ownership and type checking to compile-time prevention of concurrency mistakes.