Ir al contenido

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.)

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 function

En 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 variable cuando escribes &greeting.
  • cannot return value referencing local variable cuando llamas a un método que toma prestado, como greeting.as_str() o &scores[2..].
  • cannot return value referencing temporary value cuando los datos nunca se guardaron en una variable, como en text.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).

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.

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.

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]
▶ Pruébalo tú mismo

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]
▶ Pruébalo tú mismo

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: OK
404: Not Found
418: Unknown
▶ Pruébalo tú mismo

No 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)
▶ Pruébalo tú mismo

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.

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.

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