Cuando pasamos una variable a una función en Rust, la forma en que el compilador gestiona la propiedad (ownership) depende de si el tipo implementa el trait Copy. Para tipos que mueven su propiedad, como String, el valor se transfiere al parámetro de la función, invalidando la variable en el contexto del llamador. Para evitar la pérdida de datos, se utiliza el patrón “move in, move out”: la función consume el valor (move in) y lo devuelve mediante el tipo de retorno (move out), permitiendo que el llamador recupere el control sobre el objeto transformado.

Para realizar operaciones sobre valores que se encuentran detrás de una referencia mutante (&mut T) sin perder su propiedad, se utilizan herramientas como std::mem::replace o std::mem::swap. Estas funciones permiten extraer un valor de una posición de memoria y colocar uno nuevo de forma atómica para el modelo de propiedad, evitando dejar la memoria en un estado inválido o nulo.

Un aspecto crítico es la consistencia en las estructuras de control. El compilador de Rust rastrea el flujo de propiedad a través de las ramas (branches) de un if/else. Si en una rama se mueve el ownership de una variable hacia un nuevo ámbito o se retorna, pero en la otra rama la variable permanece intacta, el compilador exigirá que el resultado final sea consistente. Si intentas usar la variable después de un bloque donde una rama la movió y la otra no, el compilador lanzará un error porque no puede garantizar si la variable es válida o no.

use std::mem;

// Demuestra el patrón "move in, move out" devolviendo una tupla
// para retornar el nuevo ownership y metadata del original.
fn transformar_con_metadata(texto: String) -> (String, usize) {
    let longitud = texto.len();
    let transformado = texto.to_uppercase();
    (transformado, longitud) // Retornamos el nuevo String y el tamaño original
}

// Utiliza std::mem::replace para extraer un valor de una referencia mut
// y dejar un valor por defecto en su lugar.
fn extraer_con_reemplazo(contenedor: &mut String) -> String {
    mem::replace(contenedor, String::from("VACÍO"))
}

// Muestra la consistencia de ramas: el valor debe ser devuelto
// o movido en todos los caminos posibles.
fn bifurcacion_segura(condicion: bool, s: String) -> String {
    if condicion {
        // El ownership de s se mueve al retorno de la función
        s
    } else {
        // El ownership de s se libera (drop) aquí, pero devolvemos un String nuevo
        String::from("Valor por defecto")
    }
}

fn main() {
    // 1. Movimiento y retorno (Pattern: move in, move out)
    let mi_texto = String::from("hola");
    let (texto_final, tamano) = transformar_con_metadata(mi_texto);
    // println!("{}", mi_texto); // ERROR: mi_texto ya no tiene ownership
    println!("Texto: {}, Longitud original: {}", texto_final, tamano);

    // 2. Intercambio de valor con referencias mutables
    let mut buffer = String::from("Original");
    let extraido = extraer_con_reemplazo(&mut buffer);
    println!("Extraído: '{}', Buffer actual: '{}'", extraido, buffer);

    // 3. Consistencia en ramas de control
    let dato = String::from("Consistente");
    let resultado = bifurcacion_segura(true, dato);
    println!("Resultado final: {}", resultado);
}

Análisis de Código

  • transformar_con_metadata(texto: String) -> (String, usize): Esta función toma la propiedad de texto (tipo String). Devuelve una tupla que contiene un nuevo String (el ownership del texto transformado) y un usize (la longitud original). En main, la variable mi_texto queda invalidada después de esta llamada.
  • extraer_con_reemplazo(contenedor: &mut String) -> String: Recibe una referencia mutable contenedor. Mediante mem::replace, extraemos el contenido original de contenedor para devolverlo y colocamos String::from("VACÍO") en su lugar. Esto permite “sacar” el valor sin perder la propiedad de la variable buffer en main.
  • bifurcacion_segura(condicion: bool, s: String) -> String: Esta función garantiza la consistencia de propiedad. En el bloque if, s se mueve al valor de retorno. En el bloque else, s se destruye al finalizar el scope, pero la función siempre devuelve un String. Esto permite que resultado en main sea siempre válido, independientemente de la rama ejecutada.
  • Variables en main:
    • mi_texto: Un String cuyo ownership se transfiere a transformar_con_metadata.
    • texto_final: Recibe el ownership del string transformado mediante la tupla.
    • buffer: Un String mutante cuya propiedad se mantiene, pero cuyo contenido es alterado por extraer_con_reemplazo.
    • extraido: Contiene el valor que anteriormente pertenecía a buffer.
    • dato: Un String cuyo ownership entra en la lógica de decisión de bifurcacion_segura.

26