Skip to content

Commit 92a2d6d

Browse files
committed
feat: add new content on interior mutability and include new images for play and ferris scientific
1 parent 582ea0e commit 92a2d6d

File tree

5 files changed

+352
-1
lines changed

5 files changed

+352
-1
lines changed

src/es/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@
2323
- [Conceptos nuevos](./new-concepts.md)
2424
- [Traits](./new-concepts/traits.md)
2525
- [Genéricos, Traits y Dispatch](./new-concepts/generics-traits-and-static-dispatch.md)
26-
- [Smart Pointers y Heap Allocation](./new-concepts/smart-pointers.md)
26+
- [Smart Pointers y Heap Allocation](./new-concepts/smart-pointers.md)
27+
- [Mutabilidad Interna](./new-concepts/interior-mutability.md)
230 KB
Loading

src/es/images/play.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# Mutabilidad Interna
2+
3+
Cuando vimos `Box`, entendimos cómo Rust controla la propiedad de un valor en el
4+
heap.
5+
El siguiente paso es entender cómo Rust nos permite mutar valores incluso cuando
6+
no tenemos acceso mutable directo: eso es lo que llamamos mutabilidad interna.
7+
8+
En Rust, el sistema de tipos y el sistema de préstamos (borrowing) son tan
9+
poderosos que nos permiten tener mutabilidad incluso cuando el valor no es
10+
mutable en sí mismo.
11+
12+
Puede parecer contradictorio, pero es una característica intencional del
13+
lenguaje que nos permite escribir código seguro y concurrente sin sacrificar la
14+
flexibilidad.
15+
16+
En Go seria imposible mutar un valor a través de una referencia inmutable, pero en Rust
17+
18+
En Go esto no existe como concepto formal. En Go uno simplemente pasa un puntero
19+
y puede mutar el valor, porque el lenguaje permite `aliasing libre` y no impone
20+
restricciones de préstamo o propiedad, no tenemos `trait bounds` que nos ayuden
21+
a controlar el acceso a los datos, ni delimitar las posibilidades de un objeto.
22+
23+
## Aliasing
24+
25+
```go
26+
package main
27+
import "fmt"
28+
func main() {
29+
x := 10
30+
p := &x // p apunta a x
31+
q := &x // q también apunta a x
32+
*p = 20
33+
fmt.Println(x, *q) // imprime 20 20
34+
}
35+
```
36+
37+
Aquí `x` tiene aliasing, porque `p` y `q` son dos referencias que pueden mutar
38+
el mismo valor.
39+
40+
Es decir el termino `aliasing` se refiere a que el lenguaje permite tener varias
41+
referencias mutables o inmutables al mismo tiempo, sin restricciones.
42+
43+
En Go, puedes tener tantos punteros como quieras apuntando al mismo valor y
44+
mutarlos a voluntad.
45+
46+
Esto es flexible, pero también arriesgado, porque puedes crear condiciones de
47+
carrera si accedes desde múltiples threads sin locks u otros tipos de problemas
48+
con respecto a la consistencia.
49+
50+
### ¿Cómo maneja esto Rust?
51+
52+
Rust no permite aliasing libre para referencias mutables
53+
54+
Solo una referencia mutable (`&mut T`) puede existir a la vez, o varias
55+
referencias inmutables (`&T`) pueden coexistir, pero no puedes combinarlas con
56+
una mutable como hemos visto en capítulos anteriores.
57+
58+
Pero hay casos en donde quizás podamos necesitar un mecanismo similar al
59+
aliasing, no exactamente lo mismo porque no queremos que sea tan libre, pero
60+
sí que nos permita cierta flexibilidad para que algunos casos, como caches,
61+
contadores, o estructuras de datos complejas, en esos casos deberíamos poder
62+
mutar su estado interno incluso cuando no tenemos acceso mutable directo.
63+
64+
Quizás queremos implementar un comportamiento en que el usuario de nuestra
65+
estructura no necesite preocuparse por si es mutable o no, y que internamente
66+
podamos cambiar su estado.
67+
68+
Es aquí donde conceptos previamente vistos como `Smart Pointers` y
69+
`trait bounds` nos ayudan a implementar este patrón de `mutabilidad interna`.
70+
71+
Esto es algo que Go resuelve "liberando" la mutabilidad, mientras que Rust lo
72+
resuelve "restringiendo" y pidiendo al compilador que controle la seguridad.
73+
74+
## Dos nuevos Smart Pointers
75+
76+
Cuando exploramos `Box`, entendimos que Rust cuida celosamente quién es el dueño
77+
de un valor en el `heap`. Esa propiedad nos da seguridad, nadie puede tocar lo
78+
que no le pertenece. Pero la vida real, y nuestros programas, no siempre son tan
79+
rígidos. A veces necesitamos un poco de flexibilidad, queremos cambiar cosas
80+
aunque no tengamos permiso explícito, al menos en apariencia.
81+
82+
### Cell
83+
84+
Rust nos ofrece esta ventana de libertad cuidadosamente medida. Primero, con
85+
`Cell`, un contenedor pequeño y sencillo. Nos permite alterar valores simples,
86+
incluso cuando la variable que los contiene parece inmutable. Es como si Rust
87+
nos dijera: "Sí, puedes tocar esto, pero solo si sabes lo que haces y no
88+
compartes el juguete con otros al mismo tiempo".
89+
90+
Rust no nos deja hacer esto con cualquier cosa. `Cell` es para tipos que son
91+
`Copy`, es decir, tipos simples como números o booleanos. Si intentamos usarlo
92+
con algo más complejo, Rust nos detendrá en seco, también nos prohibe de usarlo
93+
en contextos concurrentes, porque ahí la seguridad es aún más crítica.
94+
95+
Rust logra esto nuevamente utilizando su sistema de tipos y `trait bounds`.
96+
`Cell` implementa el `!Sync` trait, lo que significa que no puede ser compartido
97+
entre threads. Esto nos protege de condiciones de carrera y otros problemas que
98+
podrían surgir si varios threads intentaran mutar el mismo valor al mismo
99+
tiempo.
100+
101+
Cuando veamos más adelante el capítulo de concurrencia, entenderemos mejor esto.
102+
103+
Veamos un ejemplo simple:
104+
105+
```rust
106+
use std::cell::Cell;
107+
108+
fn main() {
109+
let x = Cell::new(5);
110+
x.set(10);
111+
println!("{}", x.get()); // imprime 10
112+
}
113+
```
114+
115+
Como veremos de forma similar que con `Box` debemos de importarlo desde el
116+
módulo y luego podemos crear una `Cell` con un valor inicial, nuevamente
117+
`Cell` es un `Smart Pointer`, el valor se almacena en el heap y `Cell` mismo
118+
controlara la propiedad y el acceso a ese valor, es por eso que una vez
119+
inicializado `x` es inmutable, pero internamente podemos cambiar su valor con
120+
el método `set`, y obtener su valor con `get`, el que validara si es posible
121+
modificar el valor o no sera el propio compilador.
122+
123+
Esta estructura es considera de una abstracción de coste cero
124+
(zero-cost abstraction), porque el compilador optimiza el código para que no
125+
haya sobrecarga en tiempo de ejecución, es decir, el código generado es tan
126+
eficiente como si hubiéramos manipulado el valor directamente.
127+
128+
### RefCell
129+
130+
La realidad nos demuestra que rara vez la mutabilidad que buscamos se reduce a
131+
tipos de datos simples como enteros o booleanos. Nosotros muchas veces
132+
buscaremos mutar estructuras más complejas, para estructuras más complejas,
133+
surge `RefCell`, que nos da un poco más de libertad: podemos pedir prestadas
134+
referencias mutables a datos incluso si el contenedor parece inmutable. Rust ya
135+
no puede verificar todo en compile time, así que hace un chequeo en runtime: si
136+
intentamos ser demasiado ambiciosos y pedir mutaciones conflictivas, Rust nos
137+
detiene antes de que causemos estragos.
138+
139+
```rust
140+
use std::cell::RefCell;
141+
142+
fn main() {
143+
let data = RefCell::new(vec![1, 2, 3]);
144+
match data.try_borrow_mut() {
145+
Ok(mut_ref) => {
146+
mut_ref.push(4);
147+
}
148+
Err(_) => {
149+
println!("No se pudo obtener una referencia mutable");
150+
}
151+
}
152+
let borrowed = data.try_borrow();
153+
match borrowed {
154+
Ok(ref_ref) => {
155+
println!("{:?}", ref_ref); // imprime [1, 2, 3, 4]
156+
}
157+
Err(_) => {
158+
println!("No se pudo obtener una referencia inmutable");
159+
}
160+
}
161+
}
162+
```
163+
164+
Aquí, `RefCell` nos permite mutar un vector incluso cuando `data` es
165+
inmutable. Usamos `try_borrow_mut` para obtener una posible referencia mutable
166+
al vector y si nos lo permite el lenguaje podemos modificarlo. Si intentamos
167+
pedir prestadas múltiples referencias mutables al mismo tiempo, obtendremos un
168+
error.
169+
170+
El comportamiento de `RefCell` se basa en el conteo de referencias en tiempo de
171+
ejecución. Mantiene un registro de cuántas referencias inmutables y mutables
172+
están activas. Si intentamos pedir prestada una referencia mutable mientras hay
173+
referencias inmutables activas, o viceversa, `RefCell` nos impedirá hacerlo,
174+
lanzando un pánico en tiempo de ejecución en el caso de usar `borrow_mut` o
175+
`borrow` o retornando un `Err` en el caso de usar `try_borrow_mut` o
176+
`try_borrow`.
177+
178+
Rust permite ambos manejos de errores pero no nos permite ignorar el error,
179+
porque eso rompería la seguridad que Rust nos ofrece, es preferible mostrar
180+
un error en tiempo de ejecución que permitir un comportamiento indefinido los
181+
cuales suelen ser la causa de bugs muy difíciles de rastrear.
182+
183+
El caso de arriba es solo una ejemplificación simple, pero veamos una
184+
abstracción más realista:
185+
186+
{{#tabs }}
187+
{{#tab name="main.rs" }}
188+
189+
```rust
190+
mod client;
191+
use client::Client;
192+
use std::thread::sleep;
193+
194+
fn main() {
195+
let limiter = Client::new();
196+
197+
for i in 0..=5 {
198+
match limiter.execute_request() {
199+
Ok(_) => println!("Request {i} ejecutada con éxito"),
200+
Err(e) => println!("Error en request {i}: {e}"),
201+
}
202+
sleep(Duration::from_secs(1));
203+
}
204+
205+
println!("Uso actual: {}", limiter.current_usage());
206+
}
207+
```
208+
209+
{{#endtab }}
210+
{{#tab name="client.rs" }}
211+
212+
```rust
213+
use std::cell::RefCell;
214+
use std::time::{Duration, Instant};
215+
216+
struct Client {
217+
limit: i32,
218+
usage: RefCell<i32>, // mutabilidad interna
219+
last_request: RefCell<Option<Instant>>, // cooldown interno
220+
}
221+
222+
impl Client {
223+
fn new() -> Self {
224+
Client {
225+
limit: 5,
226+
usage: RefCell::new(0),
227+
last_request: RefCell::new(None),
228+
}
229+
}
230+
231+
fn execute_request(&self) -> Result<(), String> {
232+
// Intentamos actualizar el cooldown
233+
let Ok(mut last) = self.last_request.try_borrow_mut() else {
234+
return Err("No se pudo acceder al cooldown".to_string());
235+
};
236+
237+
let now = Instant::now();
238+
239+
if let Some(prev) = *last {
240+
if now.duration_since(prev) < Duration::from_secs(1) {
241+
return Err("Cooldown activo, espera antes de enviar otra request".to_string());
242+
}
243+
}
244+
245+
// actualizamos el cooldown
246+
*last = Some(now);
247+
248+
let Ok(mut usage) = self.usage.try_borrow_mut() else {
249+
return Err("No se pudo ejecutar la request".to_string());
250+
};
251+
252+
if *usage < self.limit {
253+
*usage += 1; // incremento secreto
254+
// Se ejecuto
255+
Ok(())
256+
} else {
257+
Err("Límite de requests alcanzado".to_string())
258+
}
259+
}
260+
261+
fn current_usage(&self) -> i32 {
262+
match self.usage.try_borrow() {
263+
Ok(u) => *u,
264+
Err(_) => {
265+
println!("No se puede leer usage ahora");
266+
0
267+
}
268+
}
269+
}
270+
}
271+
```
272+
273+
{{#endtab }}
274+
{{#endtabs }}
275+
276+
<div class="info play ferris-scientific">
277+
<p>Link de ejemplo completo en <a href="https://www.rustexplorer.com/b/z00dc1">Rust Explorer</a></p>
278+
</div>
279+
280+
En este ejemplo podemos ver cómo `Client` usa `RefCell` para manejar su estado
281+
interno de manera segura. Aunque `Client` es inmutable desde el exterior, puede
282+
mutar los atributos `usage` y `last_request` internamente gracias a `RefCell`.
283+
284+
Es una forma elegante de encapsular la mutabilidad, permitiendo que el usuario
285+
de `Client` no tenga que preocuparse por si es mutable o no, mientras que
286+
internamente `Client` puede gestionar su estado de manera segura y controlada.
287+
288+
Esta es una de las ventajas de usar Smart Pointers, podemos crear abstracciones
289+
más complejas y seguras, aprovechando el sistema de tipos y el control de
290+
préstamos de Rust.
291+
292+
Así es como podemos mantener un estado interno mutable, sin requerir al usuario
293+
de nuestra estructura que tenga que lidiar con la mutabilidad directamente.
294+
295+
## ¿Por qué es útil esto?
296+
297+
Va a ser muy útil en varios escenarios como los mencionados antes, pero
298+
es realmente útil a la hora de hacer abstracciones más complejas, porque
299+
nos permite que el usuario no se preocupe que se modifica de la variable,
300+
si tuvieramos que declarar todo como mutable, el usuario final tendría que
301+
tener un mayor conocimiento de cómo funciona nuestra estructura, y eso
302+
rompería la encapsulación.
303+
304+
En este caso nosotros no le pedimos nada al usuario, el usuario simplemente
305+
crea un `Client` y ejecutara las request y el `Client` internamente se encargara
306+
de gestionar su estado.
307+
308+
Son abstracciones inteligentes que nos permiten escribir código más limpio y
309+
seguro.
310+
311+
---
312+
313+
En el proximo capítulo veremos cómo Rust maneja la concurrencia, y veremos que
314+
estas abstracciones son aún más valiosas en ese contexto, tendremos otra
315+
abstracción que nos permitirá mutar datos de forma segura incluso en presencia
316+
de múltiples threads.
317+
318+
Asegurándonos de que no haya condiciones de carrera y nuevamente manteniendo la
319+
consistencia de los datos.
320+

theme/extra.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
border-bottom: 7px solid var(--info-border);
1717
position: relative;
1818
padding-left: 25px;
19+
min-height: 40px;
1920
}
2021

2122
.info > * {
@@ -60,6 +61,12 @@
6061
background-image: url("../images/ferris-love.png");
6162
}
6263

64+
.info.ferris-scientific::after {
65+
background-image: url("../images/ferris-scientific.png");
66+
width: 10rem;
67+
height: 7rem;
68+
}
69+
6370
.info.important {
6471
border-color: #986ee2;
6572
background-color: rgba(152, 110, 226, 0.1);
@@ -87,3 +94,23 @@ summary {
8794
margin-right: -2rem;
8895
color: var(--sidebar-fg);
8996
}
97+
98+
.info.play::before {
99+
background-image: url("../images/play.svg");
100+
fill: #347D39;
101+
color: #347D39;
102+
border-color: #347D39;
103+
content: "";
104+
font-size: 1.5rem;
105+
font-weight: bold;
106+
padding: 5px;
107+
background-size: 50%;
108+
background-repeat: no-repeat;
109+
background-position: center;
110+
margin-inline-start: calc(-2rem - 23px);
111+
}
112+
.info.play {
113+
border-color: #347D39;
114+
background-color: rgb(110 226 220 / 10%);
115+
margin-top: 0;
116+
}

0 commit comments

Comments
 (0)