¿Por qué tiene Rust String
y str
? ¿Cuáles son las diferencias entre String
y str
? ¿Cuándo se usa uno en String
lugar de str
y viceversa? ¿Uno de ellos está en desuso?
¿Por qué tiene Rust String
y str
? ¿Cuáles son las diferencias entre String
y str
? ¿Cuándo se usa uno en String
lugar de str
y viceversa? ¿Uno de ellos está en desuso?
Respuestas:
String
es el tipo de cadena dinámico dinámico, como Vec
: utilícelo cuando necesite poseer o modificar sus datos de cadena.
str
es una secuencia inmutable de 1 UTF-8 bytes de longitud dinámica en algún lugar de la memoria. Como el tamaño es desconocido, solo se puede manejar detrás de un puntero. Esto significa que str
más comúnmente 2 aparece como &str
: una referencia a algunos datos UTF-8, normalmente llamados "corte de cadena" o simplemente un "corte". Un segmento es solo una vista de algunos datos, y esos datos pueden estar en cualquier lugar, por ejemplo
"foo"
es a &'static str
. Los datos se codifican en el ejecutable y se cargan en la memoria cuando se ejecuta el programa.String
: String
desreferencias a una &str
vista de los String
datos del 's.En la pila : por ejemplo, lo siguiente crea una matriz de bytes asignada a la pila y luego obtiene una vista de esos datos como&str
:
use std::str;
let x: &[u8] = &[b'a', b'b', b'c'];
let stack_str: &str = str::from_utf8(x).unwrap();
En resumen, úselo String
si necesita datos de cadena propios (como pasar cadenas a otros hilos o construirlos en tiempo de ejecución), y úselo &str
si solo necesita una vista de una cadena.
Esto es idéntico a la relación entre un vector Vec<T>
y un corte &[T]
, y es similar a la relación entre el valor T
y la referencia &T
para los tipos generales.
1 A str
es de longitud fija; no puede escribir bytes más allá del final, ni dejar bytes finales inválidos. Como UTF-8 es una codificación de ancho variable, esto obliga a todos los str
s a ser inmutables en muchos casos. En general, la mutación requiere escribir más o menos bytes que antes (por ejemplo, reemplazar un a
(1 byte) con un ä
(2+ bytes) requeriría hacer más espacio en el str
). Existen métodos específicos que pueden modificar un &str
lugar, principalmente aquellos que manejan solo caracteres ASCII, como make_ascii_uppercase
.
2 Los tipos de tamaño dinámico permiten cosas como Rc<str>
una secuencia de bytes UTF-8 contados de referencia desde Rust 1.2. Rust 1.21 permite crear fácilmente estos tipos.
[u8; N]
.
Rc<str>
y Arc<str>
ahora se pueden usar a través de la biblioteca estándar.
Tengo experiencia en C ++ y me pareció muy útil pensar String
y &str
en términos de C ++:
String
es como un std::string
; posee la memoria y hace el trabajo sucio de administrar la memoria.&str
es como un char*
(pero un poco más sofisticado); nos señala el comienzo de un fragmento de la misma manera en que puede obtener un puntero al contenido de std::string
.¿Alguno de ellos va a desaparecer? No lo creo. Tienen dos propósitos:
String
mantiene el búfer y es muy práctico de usar. &str
es liviano y debe usarse para "mirar" en cuerdas. Puede buscar, dividir, analizar e incluso reemplazar fragmentos sin necesidad de asignar nueva memoria.
&str
puede mirar dentro de una, String
ya que puede apuntar a una cadena literal. El siguiente código debe copiar la cadena literal en la String
memoria administrada:
let a: String = "hello rust".into();
El siguiente código le permite usar el literal en sí mismo sin copia (aunque solo lectura)
let a: &str = "hello rust";
str
, solo se usa como &str
, es un segmento de cadena, una referencia a una matriz de bytes UTF-8.
String
es lo que solía ser ~str
, una matriz de bytes UTF-8 que se puede crecer y poseer.
~str
ahora esBox<str>
~str
era cultivable mientras Box<str>
que no es cultivable. (Eso ~str
y ~[T]
fueron mágicamente cultivables, a diferencia de cualquier otro objeto ~
, fue exactamente por qué String
y Vec<T>
se introdujeron, de modo que las reglas fueron directas y consistentes.)
En realidad son completamente diferentes. En primer lugar, a str
no es más que una cosa de nivel de tipo; solo se puede razonar a nivel de tipo porque es un tipo denominado de tamaño dinámico (DST). El tamaño que str
ocupa no se puede conocer en tiempo de compilación y depende de la información de tiempo de ejecución; no se puede almacenar en una variable porque el compilador necesita saber en tiempo de compilación cuál es el tamaño de cada variable. A str
es conceptualmente solo una fila de u8
bytes con la garantía de que forma un UTF-8 válido. ¿Qué tan grande es la fila? Nadie lo sabe hasta el tiempo de ejecución, por lo tanto, no se puede almacenar en una variable.
Lo interesante es que una &str
o cualquier otro puntero a una str
como Box<str>
lo hace existir en tiempo de ejecución. Este es un llamado "indicador gordo"; es un puntero con información adicional (en este caso, el tamaño de la cosa a la que apunta), por lo que es el doble de grande. De hecho, a &str
está bastante cerca de a String
(pero no a &String
). A &str
son dos palabras; un puntero al primer byte de ay str
otro número que describe cuántos bytes tiene la longitud del str
.
Contrariamente a lo que se dice, a str
no necesita ser inmutable. Si puede obtener un &mut str
como puntero exclusivo para el str
, puede mutarlo y todas las funciones seguras que lo mutan garantizan que la restricción UTF-8 se mantenga porque si se viola, entonces tenemos un comportamiento indefinido ya que la biblioteca asume que esta restricción es cierto y no lo comprueba.
Entonces, ¿qué es un String
? Eso son tres palabras; dos son los mismos que para &str
pero agrega una tercera palabra que es la capacidad del str
búfer en el montón, siempre en el montón (a str
no está necesariamente en el montón) que administra antes de que se llene y tenga que reasignarse. el String
básicamente posee una str
como dicen; lo controla y puede cambiar su tamaño y reasignarlo cuando lo considere conveniente. Entonces a String
es como se dice más cerca de a &str
que a a str
.
Otra cosa es una Box<str>
; esto también posee a str
y su representación en tiempo de ejecución es la misma que a &str
pero también posee la str
diferencia, &str
pero no puede cambiar su tamaño porque no conoce su capacidad, por lo que básicamente Box<str>
se puede ver como una longitud fija String
que no se puede cambiar de tamaño (puede siempre conviértalo en un String
si desea cambiar su tamaño).
Existe una relación muy similar entre [T]
y Vec<T>
excepto que no hay restricción UTF-8 y puede contener cualquier tipo cuyo tamaño no sea dinámico.
El uso de str
en el nivel de tipo es principalmente para crear abstracciones genéricas con &str
; existe en el nivel de tipo para poder escribir rasgos convenientemente. En teoría, str
como una cosa tipo no necesitaba existir y solo &str
eso significaría que se tendría que escribir una gran cantidad de código adicional que ahora puede ser genérico.
&str
es súper útil para poder tener múltiples subcadenas diferentes de a String
sin tener que copiar; como se dijo, String
posee el str
en el montón que administra y si solo pudiera crear una subcadena de a String
con un nuevo String
, tendría que copiarlo porque todo en Rust solo puede tener un único propietario para lidiar con la seguridad de la memoria. Entonces, por ejemplo, puede cortar una cadena:
let string: String = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];
Tenemos dos subcadenas diferentes str
de la misma cadena. string
es el propietario del str
búfer completo real en el montón y las &str
subcadenas son solo punteros gordos a ese búfer en el montón.
std::String
es simplemente un vector de u8
. Puede encontrar su definición en el código fuente . Está asignado al montón y puede crecer.
#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
str
es un tipo primitivo, también llamado corte de cadena . Un corte de cadena tiene un tamaño fijo. Una cadena literal como let test = "hello world"
tiene &'static str
tipo. test
es una referencia a esta cadena asignada estáticamente.
&str
no se puede modificar, por ejemplo,
let mut word = "hello world";
word[0] = 's';
word.push('\n');
str
tiene una porción mutable &mut str
, por ejemplo:
pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)
let mut s = "Per Martin-Löf".to_string();
{
let (first, last) = s.split_at_mut(3);
first.make_ascii_uppercase();
assert_eq!("PER", first);
assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);
Pero un pequeño cambio en UTF-8 puede cambiar la longitud de su byte, y un segmento no puede reasignar su referente.
En palabras sencillas, el String
tipo de datos se almacena en el montón (al igual que Vec
) y tiene acceso a esa ubicación.
&str
Es un tipo de corte. Eso significa que es solo referencia a un ya presente String
en algún lugar del montón.
&str
no hace ninguna asignación en tiempo de ejecución. Entonces, por razones de memoria, puede usar &str
más String
. Pero tenga en cuenta que al usarlo &str
puede tener que lidiar con vidas explícitas.
str
es que view
ya está presente String
en el montón.
Para personas de C # y Java:
String
===StringBuilder
&str
Cadena de óxido === (inmutable)Me gusta pensar en a &str
como una vista en una cadena, como una cadena interna en Java / C # donde no puede cambiarla, solo cree una nueva.
Aquí hay una explicación rápida y fácil.
String
- Una estructura de datos atribuible a almacenamiento dinámico que se puede crecer. Se puede obligar a a &str
.
str
- es (ahora, a medida que Rust evoluciona) una cadena mutable de longitud fija que vive en el montón o en el binario. Solo puede interactuar con str
un tipo prestado a través de una vista de corte de cadena, como &str
.
Consideraciones de uso:
Prefiera String
si desea poseer o mutar una cadena, como pasar la cadena a otro hilo, etc.
Prefiere &str
si quieres tener una vista de solo lectura de una cadena.
&str
está formado por dos componentes: un puntero a algunos bytes y una longitud".