Lección 10 de 26 Structs, enums y patrones
Métodos con impl
- Un bloque impl añade métodos a un tipo: funciones que usan sus datos, como area para Rectangulo.
- Se llaman con un punto: rect.area() da 6. Con &self, el método solo toma prestado el rectángulo.
- Con &mut self puede modificarlo: rect.escalar(2) dobla ancho y alto. Por eso rect es mut.
- Sin self es una función asociada y se llama con ::. Por convención, el constructor se llama new.
En la lección anterior agrupaste datos con structs. Ahora vas a darles comportamiento: funciones que pertenecen a un tipo y trabajan con sus datos. Se escriben dentro de un bloque impl (de implementation).
Si vienes de otros lenguajes, es lo más parecido a los métodos de una clase, pero con una diferencia importante: cada método declara qué hace con la instancia (leerla, modificarla o quedársela), con las mismas reglas de ownership que ya conoces.
Tu primer método
struct Rectangulo {
ancho: u32,
alto: u32,
}
impl Rectangulo {
fn area(&self) -> u32 {
self.ancho * self.alto
}
}
fn main() {
let rect = Rectangulo { ancho: 30, alto: 50 };
println!("Área: {}", rect.area());
}impl Rectangulo { ... }agrupa todo lo que pertenece al tipoRectangulo.- Un método es una función dentro de un
implcuyo primer parámetro esself: la instancia sobre la que se llama. &selfes la forma corta deself: &Self, ySelfes un alias del tipo del bloque (aquí,Rectangulo). Así queareasolo toma prestado el rectángulo para leerlo.- Se llama con un punto:
rect.area(). No hace falta escribir(&rect).area(): Rust añade el&por ti.
Podrías haber escrito una función suelta area(rect: &Rectangulo), pero el método deja claro que el área es algo propio de los rectángulos y reúne todo su comportamiento en un solo sitio.
&self, &mut self y self
El primer parámetro decide qué hace el método con la instancia:
| Primer parámetro | Qué recibe | Úsalo para |
|---|---|---|
&self | Una referencia: solo lectura | Consultar datos (el caso más habitual) |
&mut self | Una referencia mutable | Modificar campos |
self | La propiedad: consume la instancia | Transformarla o terminar con ella |
Modificar con &mut self
struct Rectangulo {
ancho: u32,
alto: u32,
}
impl Rectangulo {
fn area(&self) -> u32 {
self.ancho * self.alto
}
fn escalar(&mut self, factor: u32) {
self.ancho *= factor;
self.alto *= factor;
}
}
fn main() {
let mut rect = Rectangulo { ancho: 3, alto: 2 };
rect.escalar(10);
println!("Área: {}", rect.area());
}Para llamar a un método con &mut self, la variable tiene que ser mut, igual que para crear un &mut a mano.
Consumir con self
Un método que recibe self a secas se queda con la instancia: después de llamarlo, ya no puedes usarla.
struct Carta {
destinatario: String,
texto: String,
}
impl Carta {
fn enviar(self) {
println!("Para {}: {}", self.destinatario, self.texto);
}
}
fn main() {
let carta = Carta {
destinatario: String::from("Ferris"),
texto: String::from("¡Hola!"),
};
carta.enviar();
carta.enviar(); // la carta ya se envió
}error[E0382]: use of moved value: `carta`
--> src/main.rs:18:5
|
17 | carta.enviar();
| -------- `carta` moved due to this method call
18 | carta.enviar(); // la carta ya se envió
| ^^^^^ value used here after move
|
note: `Carta::enviar` takes ownership of the receiver `self`, which moves `carta`Es útil cuando, después de la operación, el valor original ya no tiene sentido: una carta enviada, un borrador que se convierte en publicación…
Funciones asociadas
Las funciones de un impl que no reciben self se llaman funciones asociadas. No trabajan sobre una instancia concreta, así que se llaman con el nombre del tipo y ::. Su uso más típico es crear instancias y, por convención, el constructor principal se llama new:
struct Rectangulo {
ancho: u32,
alto: u32,
}
impl Rectangulo {
fn new(ancho: u32, alto: u32) -> Self {
Self { ancho, alto }
}
fn cuadrado(lado: u32) -> Self {
Self { ancho: lado, alto: lado }
}
fn area(&self) -> u32 {
self.ancho * self.alto
}
}
fn main() {
let rect = Rectangulo::new(4, 5);
let cuadrado = Rectangulo::cuadrado(3);
println!("Áreas: {} y {}", rect.area(), cuadrado.area());
}Dentro del impl, Self equivale a Rectangulo: si algún día renombras el tipo, no tendrás que cambiarlo en cada función. Y sí, ya habías usado funciones asociadas: String::from("hola") es una función asociada del tipo String.
Ojo: A diferencia de otros lenguajes,
newno es una palabra reservada. Es una función normal con un nombre que todo el mundo usa por convención.
Punto o dos puntos
| Sintaxis | Para qué | Ejemplo |
|---|---|---|
valor.metodo() | Métodos: reciben self, &self o &mut self | rect.area() |
Tipo::funcion() | Funciones asociadas: no reciben self | Rectangulo::new(4, 5) |
Métodos que reciben otra instancia
Un método puede tener más parámetros después de self, incluida otra instancia del mismo tipo. Como solo hay que leerla, lo normal es recibirla por referencia:
struct Rectangulo {
ancho: u32,
alto: u32,
}
impl Rectangulo {
fn puede_contener(&self, otro: &Rectangulo) -> bool {
self.ancho > otro.ancho && self.alto > otro.alto
}
}
fn main() {
let grande = Rectangulo { ancho: 30, alto: 50 };
let estrecho = Rectangulo { ancho: 10, alto: 40 };
let ancho = Rectangulo { ancho: 60, alto: 45 };
println!("¿grande contiene a estrecho? {}", grande.puede_contener(&estrecho));
println!("¿grande contiene a ancho? {}", grande.puede_contener(&ancho));
}Varios bloques impl
Un tipo puede tener tantos bloques impl como quieras; el resultado es el mismo que si estuviera todo en uno:
impl Rectangulo {
fn area(&self) -> u32 {
self.ancho * self.alto
}
}
impl Rectangulo {
fn es_cuadrado(&self) -> bool {
self.ancho == self.alto
}
}Aquí no aporta nada, pero te será útil cuando llegues a los traits y los genéricos.
Consejo: Empieza siempre por
&self. Pasa a&mut selfsolo si el método modifica la instancia, y usaselfsolo cuando de verdad tenga que consumirla.
Hora de practicar
Ejercicios
0/4 resueltos
Reto 1 de 4 Resuelto
&self al bloque impl Rectangulo: perimetro, que devuelve 2 * ancho + 2 * alto, y es_mayor_que(&self, otro: &Rectangulo) -> bool, que devuelve true si el área de self es mayor que la de otro.Perímetro de a: 14 ¿a es mayor que b? true ¿b es mayor que a? false
Al ejecutar, unos tests ocultos comprueban tus funciones.
Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.