String
- Es un tipo específico en Rust para manejar cadenas de texto
- Almacenado en el Heap, permitiendo redimensionamiento dinámico (agregar, quitar o modificar caracteres)
- Tiene propiedad completa de los datos
- Más lento que los slices debido a la gestión de memoria en el Heap
- Por defecto todas las variables en Rust son inmutables
- Se convierte en mutable (si se declara como
mut)
fn main() {
// Create a mutable String
let mut greeting: String = String::from("Hello, ");
// Append the name to the greeting
greeting.push_str("John");
// Add an exclamation mark for enthusiasm
greeting.push('!');
// Print the complete greeting
println!("{}", greeting); // Outputs: Hello, John!
}
Slice
- Son un concepto general en Rust
- Relacionados con la administración de memoria y el acceso a datos
- Referencias a un segmento de una colección (array,
String, etc.) - Pueden ser de cualquier tipo de datos, no solo strings
- Inmutables y de tamaño fijo
- No poseen los datos, solo los referencian
- Se definen usando la sintaxis
&[T]donde T es el tipo de dato - En el caso de cadenas (
String), el slice se refiere con&str - Almacenados en el Stack, lo que los hace más rápidos
fn main() {
// Integer slice
let number_slice: &[i32] = &[1, 2, 3, 4, 5];
println!("Number Slice: {:?}", number_slice);
// String slice array
let animal_slice: &[&str] = &["Dog", "Cat", "Bird", "Fish"];
println!("Animal Slice: {:?}", animal_slice);
// 1. &String slice - reference to heap objects
// Taking references to String objects that are already references to heap data
let book_slice_heap: &[&String] = &[
&"The Lord of the Rings".to_string(),
&"One Hundred Years of Solitude".to_string(),
];
println!("Book Slice (Heap): {:?}", book_slice_heap);
// 2. &str slice - more memory and performance efficient
let book_slice_str: &[&str] = &[
"The Lord of the Rings",
"One Hundred Years of Solitude"
];
println!("Book Slice (&str): {:?}", book_slice_str);
}
Slice string (&str)
- Es un tipo específico de slice, especializado para strings
- No es un
Stringpor sí mismo, simplemente está "prestando" acceso a los datos sin tomar posesión de ellos - Objeto inmutable con tamaño fijo
- Puede apuntar a datos en el Stack (literales de string) o en el Heap (porciones de
String) - Se puede obtener un
&stra partir de unString, pero no al revés directamente
fn main() {
// Create a new String in the heap
let text: String = String::from("Hello, World");
let slice: &str = &text; // Points to the entire text
let partial_slice: &str = &text[0..5]; // Points to a portion of the text
println!("Full Slice: {}", slice); // "Hello, World"
println!("Partial Slice: {}", partial_slice); // "Hello"
}
Ejemplo. String, Slice y Slice string (&str)
String
- Es un tipo propio (owned) que almacena datos en el heap
- Contiene tres campos: un puntero al buffer de memoria, la longitud y la capacidad
- Tiene propiedad completa sobre los datos
&String
- Es una referencia a un String
- No tiene propiedad sobre los datos, solo los "pide prestados"
- Apunta al objeto String completo (que incluye el puntero, la longitud y la capacidad)
str
- Es un tipo "unsized" (sin tamaño conocido en tiempo de compilación)
- Representa una secuencia de caracteres UTF-8
- No puede existir por sí solo (a diferencia de u8, i32, etc.)
- Siempre se encuentra detrás de algún tipo de puntero (como
&str,Box<str>, etc.)
&str
- Es una referencia a un
str - Contiene un puntero a los datos y la longitud
- Puede referirse a texto en el stack (literales de string) o en el heap (porciones de un String)
// Función que toma diferentes tipos de parámetros
fn example(
s1: String, // String propio, toma posesión
s2: &String, // Referencia a un String
s3: &str, // Slice de string
n1: u8, // Valor numérico propio
n2: &u8 // Referencia a un valor numérico
) {
// Uso de los parámetros...
}
fn main() {
let owned_string = String::from("Hello");
let owned_number: u8 = 42;
example(
String::from("World"), // Nuevo String, se transfiere la propiedad
&owned_string, // Referencia al String existente}
"literal", // Literal de string (es un &str)
owned_number, // Copia del valor (u8 implementa Copy)
&owned_number // Referencia al número
);
// owned_string sigue siendo utilizable aquí porque solo pasamos una referencia
println!("{}", owned_string); }`
En general, para parámetros de string en funciones, es mejor usar &str en lugar de &String:
La versión con &str puede aceptar tanto &String como literales de string, mientras que la versión con &String solo acepta referencias a String.
// Mejor (más flexible)
fn user(name: &str, age: &u8) {
// Implementación...
}
// Menos flexible
fn user_restricted(name: &String, age: &u8) {
// Implementación...
}`
"Hello world" (literal de cadena)
- Es de tipo
&str(slice de string) - Tiene tamaño fijo conocido en tiempo de compilación
- Se almacena en la sección de datos del binario del programa (memoria de solo lectura)
- Es inmutable
- No tiene propiedad sobre los datos
- Tiene duración ('lifetime') estática, existe durante toda la ejecución del programa
String::from("Hello world")
- Es de tipo
String - Crea una nueva cadena en el heap
- Tiene capacidad de redimensionamiento
- Puede ser mutable (si se declara como
mut) - Tiene propiedad completa sobre los datos
- Tiene una duración limitada al scope donde se crea
- Requiere gestión de memoria (se libera automáticamente cuando sale del scope)
Ejemplo
fn main() {
// Literal de cadena - &str
let literal = "Hello world";
// String creado desde un literal
let string_owned = String::from("Hello world");
// Esto es válido porque String puede crecer
let mut mutable_string = String::from("Hello");
mutable_string.push_str(" world");
// Esto NO sería válido con un literal de cadena
// let mut mutable_literal = "Hello";
// mutable_literal.push_str(" world"); // ERROR: no método push_str para &str
// Conversión entre tipos
let literal_to_string = "Hello world".to_string(); // &str a String
let string_to_str: &str = &string_owned; // String a &str (referencia)
}
En Rust, cuando se declara una constante con const, el valor debe ser evaluable en tiempo de compilación. El problema es que String es un tipo que existe solo en tiempo de ejecución y asigna memoria en el heap, por lo que no puede ser inicializado directamente como una constante.
// Esto NO compila:
const LITERAL: String = "Hello world"; // ERROR
// Esto SÍ compila:
const LITERAL: &str = "Hello world"; // Correcto
El error ocurre porque String::from() es una función que se ejecuta en tiempo de ejecución, no en tiempo de compilación, y las constantes deben ser evaluables completamente en tiempo de compilación.
Lo que sí es posible, constantes con &str
const GREETING: &str = "Hello world";
Diferencias clave
- Un literal
"Hello world"es de tipo&'static stry vive en la memoria del programa String::from("Hello world")crea un objeto dinámico en el heap con propiedad sobre los datosconst LITERAL: &str = "Hello world"es similar al primer caso, un literal de cadena inmutable- Una constante de tipo
Stringno es posible directamente en Rust debido a cómo funciona la evaluación de constantes
La principal distinción está en dónde y cómo se almacenan los datos, y qué operaciones permiten. Las cadenas literales y constantes de tipo &str son inmutables y viven en la memoria del programa, mientras que String es una estructura de datos dinámica con memoria en el heap.