Por que Rust tem String
e str
? Quais são as diferenças entre String
e str
? Quando alguém usa em String
vez de str
e vice-versa? Um deles está sendo preterido?
Por que Rust tem String
e str
? Quais são as diferenças entre String
e str
? Quando alguém usa em String
vez de str
e vice-versa? Um deles está sendo preterido?
Respostas:
String
é o tipo de cadeia de heap dinâmico, como Vec
: use-o quando precisar possuir ou modificar seus dados de cadeia.
str
é uma sequência imutável 1 de bytes UTF-8 de comprimento dinâmico em algum lugar da memória. Como o tamanho é desconhecido, só é possível manipulá-lo atrás de um ponteiro. Isso significa que, str
geralmente, 2 aparece como &str
: uma referência a alguns dados UTF-8, normalmente chamados de "fatia de string" ou apenas "fatia". Uma fatia é apenas uma visualização de alguns dados e esses dados podem estar em qualquer lugar, por exemplo
"foo"
é a &'static str
. Os dados são codificados no executável e carregados na memória quando o programa é executado.String
: String
desreferências para uma &str
visualização dos String
dados.Na pilha : por exemplo, o seguinte cria uma matriz de bytes alocados à pilha e obtém uma visualização desses dados como&str
:
use std::str;
let x: &[u8] = &[b'a', b'b', b'c'];
let stack_str: &str = str::from_utf8(x).unwrap();
Em resumo, use String
se você precisar de dados de sequência de propriedade (como passar sequências para outros encadeamentos ou construí-los em tempo de execução) e use &str
se precisar apenas de uma exibição de uma sequência.
É idêntico ao relacionamento entre um vetor Vec<T>
e uma fatia &[T]
e é semelhante ao relacionamento entre valor T
e referência &T
para tipos gerais.
1 A str
é de comprimento fixo; você não pode escrever bytes além do final nem deixar bytes inválidos à direita. Como o UTF-8 é uma codificação de largura variável, isso efetivamente força todos os str
s a serem imutáveis em muitos casos. Em geral, a mutação requer a gravação de mais ou menos bytes do que havia antes (por exemplo, substituir um a
(1 byte) por um ä
(2 + bytes) exigiria mais espaço no str
). Existem métodos específicos que podem modificar um &str
local, principalmente aqueles que manipulam apenas caracteres ASCII, como make_ascii_uppercase
.
2 Tipos de tamanho dinâmico permitem coisas como Rc<str>
uma sequência de referência contada em UTF-8 bytes desde o Rust 1.2. O Rust 1.21 permite criar facilmente esses tipos.
[u8; N]
,.
Rc<str>
e Arc<str>
agora são utilizáveis através da biblioteca padrão.
Tenho experiência em C ++ e achei muito útil pensar String
e &str
em termos de C ++:
String
é como uma std::string
; possui a memória e faz o trabalho sujo de gerenciar memória.&str
é como um char*
(mas um pouco mais sofisticado); ele nos indica o início de um pedaço da mesma maneira que você pode obter um ponteiro para o conteúdo de std::string
.Algum deles vai desaparecer? Eu penso que não. Eles servem a dois propósitos:
String
mantém o buffer e é muito prático de usar. &str
é leve e deve ser usado para "olhar" em strings. Você pode pesquisar, dividir, analisar e até substituir pedaços sem precisar alocar nova memória.
&str
pode olhar dentro de um String
, pois pode apontar para alguma string literal. O código a seguir precisa copiar a seqüência literal na String
memória gerenciada:
let a: String = "hello rust".into();
O código a seguir permite que você use o literal em si sem copiar (somente leitura)
let a: &str = "hello rust";
str
, usado apenas como &str
, é uma fatia de sequência, uma referência a uma matriz de bytes UTF-8.
String
é o que costumava ser ~str
, uma matriz UTF-8 de bytes cultivável e de propriedade.
~str
é agoraBox<str>
~str
era cultivável enquanto Box<str>
não é cultivável. (Que ~str
e ~[T]
foram magicamente growable, diferente de qualquer outro ~
-object, foi exatamente por isso String
e Vec<T>
foram introduzidos, de modo que as regras eram todas simples e consistente.)
Eles são realmente completamente diferentes. Primeiro, a str
nada mais é que uma coisa de nível de tipo; ele só pode ser fundamentado no nível de tipo porque é o chamado tipo de tamanho dinâmico (DST). O tamanho que str
ocupa não pode ser conhecido no tempo de compilação e depende das informações de tempo de execução - não pode ser armazenado em uma variável porque o compilador precisa saber em tempo de compilação qual é o tamanho de cada variável. A str
é conceitualmente apenas uma linha de u8
bytes com a garantia de que forma UTF-8 válido. Qual é o tamanho da linha? Ninguém sabe até o tempo de execução, portanto, ele não pode ser armazenado em uma variável.
O interessante é que uma &str
ou qualquer outro ponteiro para um str
como Box<str>
faz existir em tempo de execução. Este é o chamado "ponteiro gordo"; é um ponteiro com informações extras (nesse caso, o tamanho da coisa para a qual está apontando) e, portanto, é duas vezes maior. De fato, a &str
é bem próximo a String
(mas não a &String
). A &str
são duas palavras; um ponteiro para o primeiro byte de um str
e outro número que descreve quantos bytes o comprimento str
é.
Ao contrário do que é dito, a str
não precisa ser imutável. Se você pode obter um &mut str
ponteiro exclusivo para the str
, é possível modificá-lo e todas as funções seguras que o modificam garantem que a restrição UTF-8 seja mantida, porque se isso for violado, teremos um comportamento indefinido, pois a biblioteca assume que essa restrição é true e não verifica isso.
Então, o que é um String
? São três palavras; duas são iguais a para, &str
mas adiciona uma terceira palavra que é a capacidade do str
buffer no heap, sempre no heap (a str
não está necessariamente no heap) que ele gerencia antes de ser preenchido e precisa ser realocado. o String
basicamente possui um str
como eles dizem; ele controla e pode redimensioná-lo e realocá-lo quando achar necessário. Portanto, a String
é como dito mais próximo de a do &str
que a str
.
Outra coisa é uma Box<str>
; isso também possui a str
e sua representação em tempo de execução é a mesma que a, &str
mas também possui a str
diferença de &str
mas não pode redimensioná-la porque não conhece sua capacidade; portanto, basicamente a Box<str>
pode ser vista como um comprimento fixo String
que não pode ser redimensionado (você pode sempre o converta em um String
se você quiser redimensioná-lo).
Existe um relacionamento muito semelhante entre [T]
e Vec<T>
exceto que não há restrição UTF-8 e pode conter qualquer tipo cujo tamanho não seja dinâmico.
O uso de str
no nível de tipo é principalmente para criar abstrações genéricas com &str
; existe no nível de tipo para poder escrever convenientemente traços. Em teoria, str
como um tipo de coisa não precisava existir e apenas &str
isso significava que seria necessário escrever muito código extra que agora pode ser genérico.
&str
é super útil para poder ter várias substrings diferentes de a String
sem ter que copiar; como foi dito, o String
proprietário é o str
que gerencia e, se você pudesse criar uma subcadeia de um String
com um novo String
, teria que copiar, porque tudo no Rust pode ter apenas um único proprietário para lidar com a segurança da memória. Por exemplo, você pode cortar uma string:
let string: String = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];
Temos duas substring str
s diferentes da mesma string. string
é aquele que possui o str
buffer completo real no heap e as &str
substrings são apenas indicadores de gordura para esse buffer no heap.
std::String
é simplesmente um vetor de u8
. Você pode encontrar sua definição no código fonte . É alocado para a pilha e cultivável.
#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
str
é um tipo primitivo, também chamado de fatia de string . Uma fatia de sequência tem tamanho fixo. Uma string literal como let test = "hello world"
has &'static str
type. test
é uma referência a essa sequência alocada estaticamente.
&str
não pode ser modificado, por exemplo,
let mut word = "hello world";
word[0] = 's';
word.push('\n');
str
tem fatia mutável &mut str
, por exemplo:
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);
Mas uma pequena alteração no UTF-8 pode alterar o tamanho do byte e uma fatia não pode realocar seu referente.
Em palavras fáceis, o String
tipo de dados é armazenado no heap (assim como Vec
) e você tem acesso a esse local.
&str
é um tipo de fatia. Isso significa que é apenas uma referência a um já presente String
em algum lugar da pilha.
&str
não faz nenhuma alocação em tempo de execução. Portanto, por motivos de memória, você pode usar &str
mais String
. Mas lembre-se de que, ao usar, &str
você pode ter que lidar com vidas explícitas.
str
é view
de já presente String
no heap.
Para pessoas em C # e Java:
String
===StringBuilder
&str
=== (imutável) stringEu gosto de pensar em &str
como uma exibição em uma string, como uma string interna em Java / C # onde você não pode alterá-la, apenas crie uma nova.
Aqui está uma explicação rápida e fácil.
String
- Uma estrutura de dados alocável em pilha cultivável e proprietária. Pode ser coagido a &str
.
str
- é (agora, à medida que o Rust evolui) uma sequência mutável de comprimento fixo que vive na pilha ou no binário. Você pode interagir apenas str
como um tipo emprestado por meio de uma exibição de fatia de sequência, como &str
.
Considerações de uso:
Prefira String
se você deseja possuir ou alterar uma string - como passar a string para outro thread, etc.
Prefira &str
se você deseja ter uma exibição somente leitura de uma sequência.
&str
é composto de dois componentes: um ponteiro para alguns bytes e um comprimento".