Om du frågar en C++-programmerare vad en referens är blir svaret ofta "ett alias", och en
pekare "en adress med en typ på". Rimlig gissning för Rust också — samma tecken, & och
*, samma grundidé. Men gissningen håller bara i några rader kod: en Rust-referens går att
peka om till ett helt annat värde, något som är omöjligt i C++, och kompilatorn håller reda
på exakt hur länge varje lån får leva. Och unsafe visar sig inte vara någon genväg runt
reglerna, utan en dörr rakt in i samma minnesbuggar som C++ alltid haft — use-after-free,
oinitierat minne, allt fortfarande fullt möjligt, bara att man måste be om det uttryckligen.
Den här artikeln går igenom skillnaderna ett steg i taget: modifieringsbarhet,
referenser och deras låneregler, råa pekare bakom unsafe, och till sist tre sätt att
allokera på heapen i Rust, med C++ som jämförelsepunkt hela vägen.
Variabler i Rust
En variabel deklareras i Rust med satsen
let var: type = init
Typen kan vanligtvis utelämnas eftersom kompilatorn kan klura ut detta själv. Till skillnad mot de flesta andra programmeringsspråk är variabeln konstant, dvs icke modifieringsbar, eller på engelska immutable.
Om man däremot vill deklarera en variabel som kan modifieras, trycker man in mut efter
let. Därmed blir den mutable.
let value1 = 42;
value1 += 1; //error[E0384]: cannot assign twice to immutable variable `value1`
let mut value2 = 42;
value2 += 1;
I C++ är det ju tvärtom, man trycker in const för att få en variabel immutable.
int value1 = 42;
value1++;
int const value2 = 37; // "const int" works too
value2++; //error: increment of read-only variable ‘value2’
Referenser i Rust
Referenser finns i både Rust och C++, men skiljer sig åt på flera punkter, som vi kommer att se. Att deklarera en referens i Rust görs med satsen.
let var: &type = &target
Precis som för en vanlig variabel kan referens-typen utelämnas, eftersom kompilatorn kan från
uttrycket &target lista ut att vi har en referens till ett värde av den typ target har.
Immutable refs
Notera att Rust använder och-tecknet (&) för att denotera en referens, vilket ju i
C och C++ är adressoperatorn. I Rust är allting explicit; typer, operatorer med mera.
Tja, i alla fall nästan - det finns förenklingar/genvägar. Man kommer åt värdet på en referens i
Rust via stjärna (*) vilket ju är dereferens-operatorn också i C/C++.
let target: i32 = 42;
let refvar: &i32 = ⌖
println!("target={}, *ref={}", target, *refvar);
println!("target={}, ref={}", target, refvar); //auto-deref
target=42, *ref=42
target=42, ref=42
Mutable refs
Här ovan har vi enbart variabler som är immutable. Önskar man muterbarhet, behöver man "gödsla" med operatorn mut.
I exemplet nedan har vi en mutable referens som ändrar värde på målvariabeln.
let mut value = 42;
print!("a) value={}", value);
let rf = &mut value;
println!(", ref={}", rf);
*rf = 37;
print!("b) ref={}", rf);
println!(", value={}", value);
a) value=42, ref=42
b) ref=37, value=37
En icke obetydlig semantisk skillnad mellan referenser i Rust respektive C++ är att i Rust är det en vanlig variabel med en plats och en storlek, medan i C++ är det ett alias som hanteras av kompilatorn. OK, bakom kulisserna i C++ så realiseras det som pekare, men det är ingenting vi kan använda oss av. I Rust fungerar referenser mer som pekare i C/C++, men med ganska tydliga semantiska skillnader.
Owning vs. Borrow
Äga och låna är två tydliga koncept i Rust. Varje värde har exakt en ägare, vilken ansvarar för att
frigöra eventuella resurser i samband med att värdet faller ur sitt giltighetsområde (scope). Ägarskapet
kan överföras till någon annan via tilldelning eller parameteröverföring. Detta sker implicit i Rust, medan
det är explicit i C++ via std::move (tja, implicit i vissa fall med s.k. xvalue).
Det går också att låna ett värde (borrow) i Rust och det är för detta ändamål vi har referenser. Hur detta hanteras bestäms av följande regler:
- Ett värde kan lånas immutable ett godtyckligt antal gånger samtidigt.
- Ett värde kan lånas mutable exakt en gång i taget.
- Lånetyperna kan aldrig blandas - strikt antingen eller.
- Livslängden för ett lån får aldrig överstiga livslängden för värdet som lånas ut.
Ett lån varar tills referensvariabeln faller ur sitt giltighetsområde, eller, som vi ska se strax, tills lånet inte längre behövs. OK, här kommer ett belysande exempel:
let mut value: i32 = 42;
value += 1; //this is allowed
let rf: &i32 = &value;
println!("a) value={}, ref={}", value, *rf);
//rf += 2;
//error[E0368]: binary assignment operation `+=` cannot be applied to type `&i32`
//value += 3; //this is now forbidden
//error[E0506]: cannot assign to `value` because it is borrowed
println!("b) value={}, ref={}", value, *rf);
// --- borrow later used here
print!("c) ref={}", *rf); //the borrow stops here
value += 4; //now, it's allowed again
println!(", value={}", value);
a) value=43, ref=43
b) value=43, ref=43
c) ref=43, value=47
Re-assignment
Precis som med pekare —rent allmänt— så kan referenser i Rust omtilldelas (re-assignment).
let mut first = 42;
print!("a) first={}", first);
let mut rf = &mut first;
println!(", ref={}", rf);
*rf = 37; //assign value to target
print!("b) ref={}", rf);
println!(", first={}", first);
let mut second = 17;
rf = &mut second; //assign new target
print!("c) ref={}", rf);
println!(", first={}", first);
a) first=42, ref=42
b) ref=37, first=37
c) ref=17, first=37
Detta är omöjligt i C++, utan en tilldelning propagerar till målvariabeln.
int first = 42;
int& ref = first;
std::println("a) first={}, ref={}", first, ref);
ref = 37; //assign value to target
std::println("b) first={}, ref={}", first, ref);
int second = 37;
ref = second; //also, assign value to target
std::println("c) ref={}", ref);
std::println("c) 1st={}, 2nd={}", first, second);
a) first=42, ref=42
b) first=37, ref=37
c) ref=37
c) 1st=37, 2nd=37
Composite Values
Hur fungerar det då med sammansatta värden, såsom en struct? Tja, på liknande sätt. Läs kodkommentarerna nedan noga, för att följa lånens livslängder.
#[derive(Debug)]
struct Point { x: f32, y: f32 }
//...
let mut pnt = Point { x: 5.0, y: 10.0 };
let rf: &Point = &pnt;
println!("a) value={:?}, ref={:?}", pnt, *rf);
//rf.x *= 2.5;
//^^^^^^^^^ `rf` is a `&` reference, so it cannot be written to
//error[E0594]: cannot assign to `rf.x`, which is behind a `&` reference
//pnt.x *= 2.71; //error[E0506]: cannot assign to `pnt.x` because it is borrowed
println!("b) value={:?}, ref={:?}", pnt, *rf);
// -- borrow later used here
println!("c) ref={:?}", *rf); //the borrow stops here
pnt.x *= 2.71;
pnt.y *= 3.14;
println!("c) value={:?}", pnt);
a) value=Point { x: 5.0, y: 10.0 }, ref=Point { x: 5.0, y: 10.0 }
b) value=Point { x: 5.0, y: 10.0 }, ref=Point { x: 5.0, y: 10.0 }
c) ref=Point { x: 5.0, y: 10.0 }
c) value=Point { x: 13.55, y: 31.400002 }
Pekare i Rust
Även om Rust är designat på så vis att referenser är att föredra i det flesta fall, finns det också pekare i den mer klassiska bemärkelsen. Pekare i Rust fungerar på ett sätt som har en hel del likheter, men också skillnader, jämfört med C/C++.
Precis som för övriga typer av variabler i Rust måste man skilja mellan immutable respektive mutable.
Syntaxen har vissa likheter med C/C++. Vill man ha den förstnämnda, används *const och för den
sistnämnda *mut.
let value1 = 42;
let ptr1: *const i32 = &value1; //immutable ptr to immutable value
let mut value2 = 37;
let ptr2: *mut i32 = &mut value2; //mutable ptr to mutable value
Huvudargumentet för Rust är ju safety, vilket vi redan sett exempel på i min artikelserie. Men Rust är
också ett pragmatiskt språk (precis som C++) så det finns ett sätt att temporärt stiga ur säkerhetsburen
genom att utföra vissa operationer i ett unsafe block.
Så för pekare i Rust gäller att man kan fritt deklarera dessa, men vill man komma åt det de pekar på, måste detta göras inom ett unsafe-block.
Så här gör man med en immutable pekare.
let value = 42;
let ptr: *const i32 = &value;
unsafe {
println!("val={}, ptr={}", value, *ptr);
}
val=42, ptr=42
Och så här med en mutable dito.
let mut value = 42;
let ptr: *mut i32 = &mut value;
unsafe {
println!("a) val={}, ptr={}", value, *ptr);
*ptr *= 10;
println!("b) val={}, ptr={}", value, *ptr);
}
value += 12;
unsafe {
println!("c) val={}, ptr={}", value, *ptr);
}
a) val=42, ptr=42
b) val=420, ptr=420
c) val=432, ptr=432
Heap-allokerade objekt
Pekare till stack-allokerade värden har ett begränsat användningsområde, men det är givetvis när man har heap-allokerade minnesblock/objekt som pekare är som mest användbara. Hur fungerar detta då i Rust, givet de begränsningar vi sett ovan?
Box
Om vi börjar med den rekommenderade metoden i Rust, är det att använda smarta pekare. Bibliotekstypen
Box motsvarar std::unique_ptr i C++, men med en distinkt skillnad: Box::new allokerar alltid
sitt minnesblock som en del av konstruktionen. unique_ptr kan även ta över ägarskapet av en
redan allokerad rå-pekare (t.ex. unique_ptr<int> p(new int(42))) - något Box inte har en
motsvarighet till. I övrigt fungerar de på samma sätt, när ett Box-objekt lämnar sitt giltighetsområde utförs dess
drop-funktion (motsvarar destructor) vilken frigör minnesblocket. Med andra ord, RAII används också (flitigt)
i Rust, precis som i C++. Skillnaden är syntaktisk: constructor vs. Type::new() respektive destructor vs. drop().
Så här kan det se ut:
fn scalar() {
println!("--- scalar ---");
let mut ptr: Box<i32> = Box::new(42);
println!("a) ptr={}", *ptr);
*ptr *= 10; //must deref explicit here
println!("b) ptr={}", ptr); //auto-deref
} //ptr goes out of scope here, and it will drop the memory block
#[derive(Debug)] struct Point { x: f32, y: f32 }
fn composite() {
println!("--- composite ---");
let mut ptr = Box::new(Point {x: 5.5, y: 42.0});
println!("a) ptr={:?}", *ptr);
(*ptr).x *= 10.0; //explicit deref
ptr.y *= 10.0; //auto-deref
println!("b) ptr={:?}", *ptr);
}
--- scalar ---
a) ptr=42
b) ptr=420
--- composite ---
a) ptr=Point { x: 5.5, y: 42.0 }
b) ptr=Point { x: 55.0, y: 420.0 }
Det finns ytterligare några smarta pekartyper i Rust, såsom Rc<T> och Arc<T>, men dessa får vänta
till en annan artikel.
Så Box är en typ i standardbiblioteket, vilket innebär att Box är implementerad i Rust, vilket i sin
tur innebär att på något sätt så har ju metoden new allokerat ett minnesblock och typens drop
implementation innehåller Rust-kod för att frigöra densamma. Så helt klart går det att allokera minnesblock
i Rust. Detta ska vi kika närmare på nu, via två olika vägar.
std::alloc
Först, via std::alloc vars funktioner är unsafe och därför måste hanteras inom ett unsafe-block.
Här kommer ett exempel med en skalär.
use std::alloc::{Layout, alloc, dealloc};
use std::ptr::null_mut;
//. . .
let layout = Layout::new::<i32>();
unsafe {
let mut ptr: *mut i32 = alloc(layout) as *mut i32;
println!("a) ptr={} @ {}", *ptr, ptr as usize); //oops, UB both in Rust and C++
*ptr = 42; //initialization
println!("b) ptr={} @ {}", *ptr, ptr as usize);
*ptr *= 10; //modification
println!("c) ptr={} @ {}", *ptr, ptr as usize);
dealloc(ptr as *mut u8, layout); //destruction
println!("d) ptr={} @ {}", *ptr, ptr as usize); //oops, UB here as well
ptr = null_mut(); //corresponds to nullptr in modern C++
println!("e) ptr @ {}, null={}", ptr as usize, ptr.is_null());
}
a) ptr=0 @ 100359357848912
b) ptr=42 @ 100359357848912
c) ptr=420 @ 100359357848912
d) ptr=-1268007427 @ 100359357848912
e) ptr @ 0, null=true
Varning: Redan steg
a)läser*ptrinnan minnet skrivits till -allocger ett oinitierat minnesblock, så det är också UB, även om det för en enkeli32sällan syns i praktiken. Värre är stegd), som läser*ptrefter attdeallochar frigjort minnet. Det är undefined behavior (UB) - precis lika illa som motsvarande use-after-free hade varit i C/C++. Att outputen ser ut som vanliga (om än skräpiga) heltal är ren tur; kompilatorn har i praktiken fria händer vid en UB-punkt, inklusive att optimera bort kod på sätt som gör resultatet oförutsägbart. Jag visar det ändå, för att det är exakt den typen av fotbojorunsafetar bort - och som varken referenser eller säker Rust-kod någonsin hade tillåtit.
Notera: Raden
*ptr = 42;fungerar här eftersomi32saknarDrop-implementation och alla bitmönster är giltigai32-värden. Det idiomatiska sättet att skriva till nyallokerat, oinitierat minne är annarsstd::ptr::write(ptr, 42)— en vanlig dereferens-tilldelning (*ptr = value) tolkas som "droppa det som redan finns på*ptr, skriv sedanvalue", och där finns inget giltigt att droppa än. För en typ med en riktigDrop-implementation hade*ptr = valuealltså varit UB, av samma skäl som ovan.
Variabeln layout kommer lite otippat, men innehåller metadata om minnesblockets
storlek i antalet bytes (size), plus information om blockets adressjustering (alignment).
pub struct Layout {
size_: usize,
align_: NonZeroUsize,
}
I C++17 kom nya varianter av operatorerna new och delete, vilka medgav att
man kan skicka med förutom antalet bytes, också vilken adressjustering som ska användas.
Så här kan det se ut:
#include <new>
//operator declaration:
operator new(size_t, std::align_val_t)
operator delete(void*, std::align_val_t) noexcept
//operator usage:
auto ptr = new (std::align_val_t{64}) int{12345};
//...usage of the memory block...
::operator delete(ptr, std::align_val_t{64});
(Anropet av delete görs som ett vanligt funktionsanrop eftersom det inte finns någon "placement"-variant
av delete-uttrycket i C++ — grammatiken tillåter bara delete expr, inga extra argument.
Denna operator delete-overload, en s.k. placement deallocation function, anropas annars
enbart automatiskt av kompilatorn om konstruktorn i ett placement new-uttryck kastar ett
undantag.)
Allokerar man ett minnesblock för en sammansatt datatyp, kan det se ut så här.
let layout = Layout::new::<Point>();
unsafe {
let ptr: *mut Point = alloc(layout) as *mut Point;
println!("a) ptr={:?} @ {}", *ptr, ptr as usize);
(*ptr).x = 10.0;
(*ptr).y = 5.0;
println!("b) ptr={:?} @ {}", *ptr, ptr as usize);
dealloc(ptr as *mut u8, layout);
println!("c) ptr={:?} @ {}", *ptr, ptr as usize); //oops, UB
}
a) ptr=Point { x: -2.1955198e-7, y: 7e-45 } @ 100359357848912
b) ptr=Point { x: 10.0, y: 5.0 } @ 100359357848912
c) ptr=Point { x: -2.1955198e-7, y: 7e-45 } @ 100359357848912
Varning: Precis som i exemplet ovan är steg
c)UB -deallochar redan frigjort minnet när vi läser*ptrigen.
Notera: Samma sak här som i skalar-exemplet ovan -
(*ptr).x = 10.0;och(*ptr).y = 5.0;skriver fält för fält till oinitierat minne. Fungerar eftersomf32saknarDrop-implementation, men det idiomatiska sättet ärstd::ptr::write(ptr, Point { x: 10.0, y: 5.0 })- skriv hela värdet på en gång istället för att gå via fältprojektion på oinitierat minne.
Notera:
(*ptr).xser ut som klassisk C, men det är faktiskt den enda syntaxen - Rust har ingen arrow-operator (->), och till skillnad från referenser gör punktoperatorn ingen auto-deref genom råa pekare.ptr.x(utan den explicita*) gererror[E0609]: no field x on type *mut Point, med kompilatorförslaget "try dereferencing it: (*ptr).x". Samma sak gäller metodanrop på råa pekare. Auto-deref via.är reserverat för referenser och smarta pekare (viaDeref-traiten), inte för*const/*mut.
libc::malloc/free
Rust på Linux länkar dynamiskt libc (standardbiblioteket för C), vilket innebär att man har tillgång
till "good old malloc/free". I gengäld måste lejonparten av koden avseende allokering och access
ligga inom ett unsafe-block.
#[derive(Debug)]
struct Point { x: f32, y: f32 }
pub fn main() {
unsafe {
let mut ptr: *mut Point = libc::malloc( size_of::<Point>() ) as *mut Point;
if ptr.is_null() {
panic!("malloc failed");
}
(*ptr).x = 10.0;
(*ptr).y = 5.0;
println!("a) ptr={:?} @ {}", *ptr, ptr as usize);
libc::free(ptr as *mut libc::c_void);
println!("b) ptr={:?} @ {}", *ptr, ptr as usize); //oops, UB
ptr = 0 as *mut Point;
println!("c) ptr: null={} @ {}", ptr.is_null(), ptr as usize);
}
}
a) ptr=Point { x: 10.0, y: 5.0 } @ 94361111567696
b) ptr=Point { x: 7.3198564e17, y: 7e-45 } @ 94361111567696
c) ptr: null=true @ 0
Varning: Samma sak här - steg
b)läser*ptrefterlibc::free, alltså UB av samma skäl som istd::alloc-exemplen ovan.
Sammanfattning
Rust och C++ delar vokabulär - referenser, pekare, mutability - men semantiken skiljer sig rejält
under ytan. I Rust är immutable default, och en referens (&) är en fullvärdig variabel med egen
plats i minnet, inte bara ett kompilatoralias som i C++. Det är också därför en Rust-referens går
att peka om (re-assignment), medan en C++-referens är låst till sitt mål för alltid. Ownership-
och borrow-reglerna - ett mutable-lån åt gången, aldrig blandat med immutable-lån, och lånets
livslängd får aldrig överstiga värdets - är kompilatorkontrollerade och stoppar hela kategorier av
buggar redan vid cargo build, något C++ helt enkelt inte gör åt dig.
Pekare i klassisk mening (*const/*mut) finns kvar i Rust, men är instängda bakom unsafe-block
så fort man vill dereferencera dem. Samma story gäller heap-allokering: Box är den rekommenderade
vägen och sköter frigörande automatiskt via drop, medan std::alloc och libc::malloc/free ger
samma råa kontroll - och samma fallgropar - som i C. Use-after-free och läsning av oinitierat minne
är fullt möjligt även i Rust; skillnaden är att språket tvingar en att skriva unsafe för att öppna
dörren till dem.
Källkoden
All kod i artikeln finns körklar i ett eget GitHub-repo, länkat sist i länksamlingen nedan.
Rust-sidan byggs med Cargo (varje exempel är ett eget [[bin]]):
cargo build
cargo run --bin reference_demo
C++-sidan byggs med CMake:
cmake -B build-cxx -G Ninja
cmake --build build-cxx
./build-cxx/reference_demo
Länkar
Senaste Artiklarna
-
Referenser & pekare i Rust jämfört med C++
28 augusti 2026 -
När Rust får panik gör C++ ett undantag
21 augusti 2026 -
Rust syntax jämfört med C++
14 augusti 2026 -
Fem skäl att välja Rust över C++
10 augusti 2026 -
Apollo 11 och kvinnan bakom koden
12 april 2026 -
Hur gör man en Ralph Loop?
6 april 2026 -
Kan du alla nya AI termer?
22 mars 2026 -
OpenClaw - Det ska vara en hummer i år
15 mars 2026