Error E0515 de Rust: cannot return reference to local variable
E0515 significa: tu función intenta devolver una referencia (&) a algo que ella misma creó. Todo lo que crea una función se limpia cuando la función termina, así que la referencia apuntaría a la nada. Casi todo el que empieza con Rust se topa con este error, y la solución suele ser quitar un solo & y devolver el valor en sí. (Esta página es parte de Errores del compilador de Rust explicados.)
El mensaje de error
Sección titulada «El mensaje de error»error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ greeting se libera al volver de la función | ^^^^^^^^^ returns a reference to data owned by the current functionEn español: “no se puede devolver una referencia a la variable local greeting”, porque “devuelve una referencia a datos que pertenecen a la función actual”.
Este es el programa que lo provoca:
fn make_greeting(name: &str) -> &str { let greeting = format!("Hello, {name}!"); // un String nuevo, cuyo dueño es esta función &greeting // ❌ greeting se libera al volver de la función}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}Verás varias formas de escribir el mismo error. Todas significan lo mismo:
cannot return reference to local variablecuando escribes&greeting.cannot return value referencing local variablecuando llamas a un método que toma prestado, comogreeting.as_str()o&scores[2..].cannot return value referencing temporary valuecuando los datos nunca se guardaron en una variable, como entext.to_uppercase().as_str().
Todas las versiones llevan la etiqueta “returns a value referencing data owned by the current function” (devuelve un valor que hace referencia a datos de la función actual).
Qué significa
Sección titulada «Qué significa»greeting es una variable local: se crea dentro de make_greeting y su dueño es esa función. Cuando la función termina, todas sus variables locales se liberan. El &str que devuelves apuntaría dentro de un String que ya no existe.
Es como mudarte de un piso y darle la llave a un amigo. La llave es real, pero el piso ahora está vacío, o vive otra persona en él. Tu amigo necesita los muebles, no la llave.
Una forma muy común de llegar aquí: primero escribiste greeting (un String) en una función que devuelve &str, te salió E0308 (mismatched types, tipos que no coinciden), y el compilador sugirió “consider borrowing here: &greeting”. Esa sugerencia arregla el tipo, pero te lleva directo a E0515. La solución de verdad es cambiar el tipo de retorno.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Una referencia nunca debe vivir más que el valor al que apunta. Devolver una referencia a una variable local crearía una dangling reference (referencia colgante), un puntero a memoria ya liberada. En C y C++ esto compila y luego falla (o peor, devuelve basura sin avisar). Rust lo detecta al compilar.
Una referencia devuelta tiene que tomar prestado de algo que vive fuera de la función: un parámetro que te pasó quien llama, un campo de self, o un valor 'static como un literal de cadena. Las variables locales de la propia función no sirven. Por eso las anotaciones de lifetime no pueden arreglar E0515: 'a solo describe cuánto viven los datos, no puede hacer que una variable local viva más. Mira Ownership y Borrowing.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Devuelve el valor propio
Sección titulada «Solución 1: Devuelve el valor propio»Elige esta cuando la función crea los datos. Es la respuesta correcta la mayoría de las veces. Devuelve String en lugar de &str (o Vec<T> en lugar de &[T]) y quita el &. El valor se mueve hacia quien llama, que pasa a ser su dueño:
fn make_greeting(name: &str) -> String { let greeting = format!("¡Hola, {name}!"); greeting // ✅ le entregamos el String entero a quien llama}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // las dos más altas, en un Vec nuevo}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}Salida:
¡Hola, Ferris![88, 97]No te preocupes por el coste: devolver un String o un Vec solo mueve un pequeño puntero, la longitud y la capacidad. El texto en sí no se copia. También podrías escribir simplemente format!("¡Hola, {name}!") como última línea.
Solución 2: Recibe los datos como parámetro y devuelve una referencia a ellos
Sección titulada «Solución 2: Recibe los datos como parámetro y devuelve una referencia a ellos»Elige esta cuando quien llama ya es dueño de los datos y tú solo quieres apuntar a una parte. Si la función trabaja sobre un parámetro prestado, la referencia devuelta toma prestado de ese parámetro, que vive fuera de la función:
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ apunta a los datos de quien llama}
fn main() { let mut scores = vec![42, 97, 63, 88]; // su dueño es main let best = top_two(&mut scores); println!("{best:?}");}Salida:
[88, 97]Con un solo parámetro que es referencia, Rust rellena el lifetime por ti. Con dos o más, puede que necesites 'a (mira E0106). Esto solo funciona si devuelves un trozo de la entrada. Un valor nuevo construido a partir de la entrada (to_uppercase(), format!, trim().to_string()) vuelve a ser una variable local, así que regresa a la Solución 1. Mira Slices.
Solución 3: Devuelve &'static str para literales de cadena
Sección titulada «Solución 3: Devuelve &'static str para literales de cadena»Elige esta cuando cada resultado posible es un texto fijo escrito en tu código. Los literales de cadena se guardan dentro del propio programa y viven para siempre, así que devolver una referencia a uno siempre es seguro:
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Unknown", }}
fn main() { for code in [200, 404, 418] { println!("{code}: {}", status_text(code)); }}Salida:
200: OK404: Not Found418: UnknownNo intentes “arreglar” el resultado de un format! escribiendo &'static str en la firma. Solo conseguirás E0515 otra vez, porque el String sigue siendo local.
Solución 4: Usa Cow cuando a veces tomas prestado y a veces creas
Sección titulada «Solución 4: Usa Cow cuando a veces tomas prestado y a veces creas»Elige esta cuando la función normalmente devuelve su entrada sin cambios, pero a veces tiene que construir una cadena nueva. Cow (clone-on-write, “clonar al escribir”) guarda o bien un &str prestado o bien un String propio, así que solo reservas memoria cuando hace falta:
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // un String nuevo solo cuando hace falta } else { Cow::Borrowed(name) // ninguna copia }}
fn main() { for name in ["informe.txt", "mis notas.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "prestado", Cow::Owned(_) => "propio", }; println!("{clean} ({kind})"); }}Salida:
informe.txt (prestado)mis_notas.txt (propio)Cow es una optimización. Si no estás seguro de necesitarlo, devuelve un String (Solución 1). Más detalles en Weak y Cow.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Devolver s.as_str() de un String local. greeting.as_str() toma prestado greeting, así que falla igual que &greeting, con el texto “cannot return value referencing local variable”. Solución: devuelve greeting y cambia el tipo de retorno a String.
Un .map() que crea cadenas nuevas. Parece inofensivo, pero cada to_lowercase() crea un String temporal que se libera justo después de .as_str():
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}Solución: devuelve Vec<String> y quita .as_str(): .map(|t| t.trim().to_lowercase()).collect(). (Sin to_lowercase(), t.trim() es un slice de input, así que Vec<&str> sí funcionaría. Mira Iteradores.)
Un método que formatea un campo. fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } da “cannot return reference to temporary value”. Solución: -> String. Devolver &self.first está bien, porque esos datos viven en el struct, no en el método. Mira Structs.
Una función sin parámetros que sean referencias. fn make_greeting() -> &String falla antes, con E0106, porque no hay nada de lo que pueda tomar prestado. La solución es la misma: devuelve String.
Errores relacionados
Sección titulada «Errores relacionados»- E0106: missing lifetime specifier: suele aparecer primero; añadir
'ano arregla una referencia a una variable local. - E0597: borrowed value does not live long enough: el mismo problema dentro de una sola función, con un bloque o un bucle en lugar de un
return. - E0308: mismatched types: devolver un
Stringdonde se declaró&str. Cambia el tipo de retorno, no añadas&. - E0382: borrow of moved value: cuando ya usas valores propios, ten cuidado de no usar un valor después de moverlo.
- E0716: temporary value dropped while borrowed: una referencia a un valor temporal que se usa más adelante en la misma función.
- Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. Una función construye un mensaje con format! y devuelve &message. ¿Cuál suele ser la solución correcta?
2. ¿Qué función compila?
3. ¿Cuándo es Cow<str> un buen tipo de retorno?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0515 — Rust error codes index (en inglés): la explicación oficial de este error.
- Dangling References — The Rust Book (en inglés): el ejemplo
dangley por qué la respuesta es devolver elStringdirectamente. - Validating References with Lifetimes — The Rust Book (en inglés): por qué una referencia devuelta tiene que estar ligada a uno de los parámetros.
- The Slice Type — The Rust Book (en inglés): devolver slices
&strque toman prestado de la entrada, la versión segura de la Solución 2. Cow— documentación de la biblioteca estándar (en inglés): el tipo clone-on-write de la Solución 4, con ejemplos.