Reference counting
27 ноември 2025
Reference counting

Reference counting
1
2
3
4
5
6
7
8
9
use std::rc::Rc;
fn main() {
let first = Rc::new(String::from("foobar"));
let second = Rc::clone(&first);
println!("{}", first);
println!("{}", second);
}
foobar foobar
use std::rc::Rc;
fn main() {
let first = Rc::new(String::from("foobar"));
let second = Rc::clone(&first);
println!("{}", first);
println!("{}", second);
}
Reference counting
Rc::newалокира подадената си стойност на heap-а, подобно на Box- и задава ref count = 1
Reference counting
Rc::newалокира подадената си стойност на heap-а, подобно на Box- и задава ref count = 1
Rc::cloneдава новоRc, което вътрешно сочи до същата стойностRcподдържа ownership над стойността -- споделен ownershipRc::cloneувеличава ref count += 1
Reference counting
Rc::newалокира подадената си стойност на heap-а, подобно на Box- и задава ref count = 1
Rc::cloneдава новоRc, което вътрешно сочи до същата стойностRcподдържа ownership над стойността -- споделен ownershipRc::cloneувеличава ref count += 1
- когато
Rcсе drop-не (деструктира)- ref count -= 1
- когато ref count падне до 0, стойността се деалокира
Reference counting
Проблем: стойността е read-only:
1
2
3
4
5
let mut a = Rc::new(3);
*a = 5;
println!("{:?}", a);
error[E0594]: cannot assign to data in an `Rc` --> src/bin/main_daef8d7d55ceabff0dc2ebe0ac97186bb40b3b39.rs:5:1 | 5 | *a = 5; | ^^^^^^ cannot assign | = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<i32>` For more information about this error, try `rustc --explain E0594`. error: could not compile `rust` (bin "main_daef8d7d55ceabff0dc2ebe0ac97186bb40b3b39") due to 1 previous error
use std::rc::Rc;
fn main() {
let mut a = Rc::new(3);
*a = 5;
println!("{:?}", a);
}
Reference counting
Rcне ни позволява да взимаме mutable reference към пазената стойност
Reference counting
Rcне ни позволява да взимаме mutable reference към пазената стойност- това би нарушило ограничението за един
&mut T/ много&T
Reference counting
Rcне ни позволява да взимаме mutable reference към пазената стойност- това би нарушило ограничението за един
&mut T/ много&T- защото можем да имаме две
Rc-та към една и съща стойност - ако през първото вземем
&Rc->&T - и през второто вземем
&mut Rc->&mut T - бихме нарушили правилата
- защото можем да имаме две
Reference counting
Rcне ни позволява да взимаме mutable reference към пазената стойност- това би нарушило ограничението за един
&mut T/ много&T- защото можем да имаме две
Rc-та към една и съща стойност - ако през първото вземем
&Rc->&T - и през второто вземем
&mut Rc->&mut T - бихме нарушили правилата
- защото можем да имаме две
- но въпреки това има начини да модифицираме пазената стойност
Internal mutability

Internal mutability
RefCell
1
2
3
4
5
6
7
8
9
10
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // няма `mut`
println!("{}", cell.borrow()); // -> std::cell::Ref<String>
cell.borrow_mut().push_str("bar"); // -> std::cell::RefMut<String>
println!("{}", cell.borrow()); // -> std::cell::Ref<String>
}
foo foobar
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // няма `mut`
println!("{}", cell.borrow()); // -> std::cell::Ref
cell.borrow_mut().push_str("bar"); // -> std::cell::RefMut
println!("{}", cell.borrow()); // -> std::cell::Ref
}
Internal mutability
RefCell
1
2
3
4
5
6
7
8
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // отново няма `mut`
let mut first = cell.borrow_mut();
let mut second = cell.borrow_mut(); // BOOM!
}
thread 'main' panicked at src/bin/main_45bb39151537827e2a223fcdd3995ba26df346ef.rs:9:27: RefCell already borrowed note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
#![allow(unused_variables)]
#![allow(unused_mut)]
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // отново няма `mut`
let mut first = cell.borrow_mut();
let mut second = cell.borrow_mut(); // BOOM!
}
Internal mutability
RefCell
- Runtime borrow checker
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()ще върне структура от типRef, която има deref до&T
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()ще върне структура от типRef, която има deref до&Tborrow_mut()ще върне структура от типRefMut, която има deref до&mut T
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()ще върне структура от типRef, която има deref до&Tborrow_mut()ще върне структура от типRefMut, която има deref до&mut T- ако не можем да вземем референция по стандартните правила на borrow checker-а ще получим
panic!вместо компилаторна грешка
Internal mutability
Често RefCell се използва в комбинация с Rc
1
2
3
4
5
6
7
8
9
10
11
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let first = Rc::new(RefCell::new(String::from("foo")));
let second = Rc::clone(&first);
first.borrow_mut().push_str("bar");
println!("{}", second.borrow());
}
foobar
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let first = Rc::new(RefCell::new(String::from("foo")));
let second = Rc::clone(&first);
first.borrow_mut().push_str("bar");
println!("{}", second.borrow());
}
Internal mutability
Cell
Още един, по-ограничен тип
- използва се за
Copyтипове getвръща копие на пазената стойност (изискваCopy)setпрезаписва пазената стойност с новата- не можем да вземем референция (
&/&mut) към вътрешната стойност
Internal mutability
1
2
3
4
5
6
7
8
9
10
11
12
13
use std::cell::Cell;
use std::rc::Rc;
fn main() {
let first = Rc::new(Cell::new(10));
let second = Rc::clone(&first);
println!("{}", second.get());
first.set(42);
println!("{}", second.get());
}
10 42
use std::cell::Cell;
use std::rc::Rc;
fn main() {
let first = Rc::new(Cell::new(10));
let second = Rc::clone(&first);
println!("{}", second.get());
first.set(42);
println!("{}", second.get());
}
Reference counting
reference cycles
Какво правим, когато структурата ни може да има цикли?
1
2
3
4
5
6
7
8
9
10
11
struct TreeNode {
value: u32,
parent: Option<Rc<RefCell<TreeNode>>>,
children: Vec<Rc<RefCell<TreeNode>>>,
}
impl TreeNode {
fn new(value: u32, parent: Option<Rc<RefCell<TreeNode>>>) -> Rc<RefCell<TreeNode>> {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
#![allow(dead_code)]
use std::rc::Rc;
use std::cell::RefCell;
//norun
struct TreeNode {
value: u32,
parent: Option>>,
children: Vec>>,
}
impl TreeNode {
fn new(value: u32, parent: Option>>) -> Rc> {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
fn main() {}
Reference counting
Side note
Може да си улесните малко живота с type alias:
1
2
3
4
5
6
7
8
9
10
11
12
13
type TreeNodeRef = Rc<RefCell<TreeNode>>;
struct TreeNode {
value: u32,
parent: Option<TreeNodeRef>,
children: Vec<TreeNodeRef>,
}
impl TreeNode {
fn new(value: u32, parent: Option<TreeNodeRef>) -> TreeNodeRef {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
#![allow(dead_code)] use std::rc::Rc; use std::cell::RefCell; //norun type TreeNodeRef = Rc>; struct TreeNode { value: u32, parent: Option , children: Vec , } impl TreeNode { fn new(value: u32, parent: Option ) -> TreeNodeRef { Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] })) } } fn main() {}
Reference counting
reference cycles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn make_tree() -> Rc<RefCell<TreeNode>> {
let root = TreeNode::new(0, None);
let v1 = TreeNode::new(1, Some(Rc::clone(&root)));
let v2 = TreeNode::new(2, Some(Rc::clone(&root)));
{
let mut r = root.borrow_mut();
r.children.push(v1);
r.children.push(v2);
}
root
}
fn main() {
let tree = make_tree();
println!("{:?}", tree.borrow().value);
mem::drop(tree);
}
0
#![allow(dead_code)]
use std::rc::Rc;
use std::cell::RefCell;
use std::mem;
#[derive(Debug)]
struct TreeNode {
value: u32,
parent: Option>>,
children: Vec>>,
}
impl TreeNode {
fn new(value: u32, parent: Option>>) -> Rc> {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
fn make_tree() -> Rc> {
let root = TreeNode::new(0, None);
let v1 = TreeNode::new(1, Some(Rc::clone(&root)));
let v2 = TreeNode::new(2, Some(Rc::clone(&root)));
{
let mut r = root.borrow_mut();
r.children.push(v1);
r.children.push(v2);
}
root
}
fn main() {
let tree = make_tree();
println!("{:?}", tree.borrow().value);
mem::drop(tree);
}
Reference counting
reference cycles
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
Reference counting
reference cycles
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
- получаваме цикъл от референции -- никога няма да се деалокират
Reference counting
reference cycles
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
- получаваме цикъл от референции -- никога няма да се деалокират
- това води до изтичане на памет
Reference counting
Side note
- забележете, че можем да имаме memory leak в safe code
Reference counting
Side note
- забележете, че можем да имаме memory leak в safe code
- затова нямаме гаранция че деструкторите ще се извикат
Reference counting
Side note
- забележете, че можем да имаме memory leak в safe code
- затова нямаме гаранция че деструкторите ще се извикат
- и затова съществува безопасната функция
mem::forget(https://doc.rust-lang.org/std/mem/fn.forget.html#safety)
Weak reference
Да се върнем на проблема с дървото
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
- не искаме детето да е собственик на родителя
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
- не искаме детето да е собственик на родителя
- за това се използват силни и слаби референции
Weak reference
1
2
3
downgrade: fn(Rc) -> Weak
upgrade: fn(Weak) -> Option<Rc>
- слабата референция (
std::rc::Weak) сочи към стойността, но не я поддържа жива - има отделни броячи
- stong ref count - за
std::rc::Rc - weak ref count - за
std::rc::Weak
- stong ref count - за
- когато броя силни рефениции стане 0 - стойността се деалокира
- дори и да има още слаби референции към нея
Weak reference
Ето как изглежда в паметта:

Weak reference
1
2
3
4
5
6
7
8
9
10
11
12
use std::mem;
use std::rc::{Rc, Weak};
fn main() {
let rc = Rc::new(10);
let weak = Rc::downgrade(&rc);
println!("{:?}", Weak::upgrade(&weak)); // Option<Rc<T>>
mem::drop(rc);
println!("{:?}", Weak::upgrade(&weak)); // Option<Rc<T>>
}
Some(10) None
use std::mem;
use std::rc::{Rc, Weak};
fn main() {
let rc = Rc::new(10);
let weak = Rc::downgrade(&rc);
println!("{:?}", Weak::upgrade(&weak)); // Option>
mem::drop(rc);
println!("{:?}", Weak::upgrade(&weak)); // Option>
}
Weak references
Пример
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let gosho_source = "Гошо, Гошо, скочи лошо";
let shared_gosho = Rc::new(gosho_source); // shared_gosho { strong = 1, weak = 0 };
let bratcheda = Rc::clone(&shared_gosho); // shared_gosho { strong = 2, weak = 0 };
// или, shared_gosho.clone(), но първото е по-ясно
let slabichko = Rc::downgrade(&shared_gosho); // shared_gosho { strong = 2, weak = 1 };
println!("{:#?}", Weak::upgrade(&slabichko)); // => Some("Гошо, Гошо, скочи лошо")
// shared_gosho { strong = 3, weak = 1 };
// shared_gosho { strong = 2, weak = 1 };
std::mem::drop(bratcheda); // shared_gosho { strong = 1, weak = 1 };
std::mem::drop(shared_gosho); // shared_gosho { strong = 0, weak = 1 }; => DROP!
println!("{:#?}", Weak::upgrade(&slabichko)); // => None
Weak references
Пример
1
2
3
4
5
6
7
8
// Инициализираме споделената стойност
let gosho_source = "Гошо, Гошо, скочи лошо";
let shared_gosho = Rc::new(gosho_source); // Rc<&str>
let bratcheda = Rc::clone(&shared_gosho); // Rc<&str>
let slabichko = Rc::downgrade(&shared_gosho); // Weak<&str>
println!("{:#?}", Weak::upgrade(&slabichko)); // Option<Rc<&str>>