Quais são as diferenças entre `String` e` str` de Rust?

420

Por que Rust tem Stringe str? Quais são as diferenças entre Stringe str? Quando alguém usa em Stringvez de stre vice-versa? Um deles está sendo preterido?

Daniel Fath
fonte

Respostas:

491

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, strgeralmente, 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

  • No armazenamento estático : uma string literal "foo"é a &'static str. Os dados são codificados no executável e carregados na memória quando o programa é executado.
  • Dentro de um heap alocadoString : Stringdesreferências para uma &strvisualização dos Stringdados.
  • 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 Stringse 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 &strse 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 Te referência &Tpara 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 strs 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 &strlocal, 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.

huon
fonte
10
"sequência de bytes UTF-8 ( de tamanho desconhecido )" - isso está desatualizado? Os documentos dizem que "A &stré composto de dois componentes: um ponteiro para alguns bytes e um comprimento".
mrec 10/10
11
Não está desatualizado (essa representação tem sido bastante estável), apenas um pouco imprecisa: não é conhecida estaticamente, ao contrário, digamos [u8; N],.
huon
2
@rec é desconhecido no momento da compilação, não é possível fazer suposições sobre o tamanho, por exemplo, ao criar um quadro de pilha. Por isso, é frequentemente tratado como uma referência, que é um tamanho conhecido em tempo de compilação, que é o tamanho de um ponteiro.
Sekhat
1
Atualização: Rc<str>e Arc<str>agora são utilizáveis ​​através da biblioteca padrão.
Centril 25/03/19
1
@cjohansson Objetos alocados estaticamente normalmente não são armazenados nem na pilha nem na pilha, mas em sua própria região de memória.
Brennan Vincent
97

Tenho experiência em C ++ e achei muito útil pensar Stringe &strem termos de C ++:

  • Uma ferrugem Stringé como uma std::string; possui a memória e faz o trabalho sujo de gerenciar memória.
  • Um Rust &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:

Stringmanté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.

&strpode olhar dentro de um String, pois pode apontar para alguma string literal. O código a seguir precisa copiar a seqüência literal na Stringmemó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";
Luis Ayuso
fonte
13
como uma string_view?
Abhinav Gauniyal
2
Sim, como string_view, mas intrínseco ao idioma e emprestar adequadamente verificado.
locka
41

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.

Chris Morgan
fonte
Tecnicamente, o que costumava ser ~stré agoraBox<str>
jv110
3
@ jv110: não, porque ~strera cultivável enquanto Box<str>não é cultivável. (Que ~stre ~[T]foram magicamente growable, diferente de qualquer outro ~-object, foi exatamente por isso Stringe Vec<T>foram introduzidos, de modo que as regras eram todas simples e consistente.)
Chris Morgan
18

Eles são realmente completamente diferentes. Primeiro, a strnada 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 strocupa 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 u8bytes 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 &strou qualquer outro ponteiro para um strcomo 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 &strsão duas palavras; um ponteiro para o primeiro byte de um stre outro número que descreve quantos bytes o comprimento stré.

Ao contrário do que é dito, a strnão precisa ser imutável. Se você pode obter um &mut strponteiro 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, &strmas adiciona uma terceira palavra que é a capacidade do strbuffer no heap, sempre no heap (a strnão está necessariamente no heap) que ele gerencia antes de ser preenchido e precisa ser realocado. o Stringbasicamente possui um strcomo 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 &strque a str.

Outra coisa é uma Box<str>; isso também possui a stre sua representação em tempo de execução é a mesma que a, &strmas também possui a strdiferença de &strmas não pode redimensioná-la porque não conhece sua capacidade; portanto, basicamente a Box<str>pode ser vista como um comprimento fixo Stringque não pode ser redimensionado (você pode sempre o converta em um Stringse 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 strno 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, strcomo um tipo de coisa não precisava existir e apenas &strisso 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 Stringsem ter que copiar; como foi dito, o String proprietário é o strque gerencia e, se você pudesse criar uma subcadeia de um Stringcom 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 strs diferentes da mesma string. stringé aquele que possui o strbuffer completo real no heap e as &strsubstrings são apenas indicadores de gordura para esse buffer no heap.

Zorf
fonte
4

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 strtype. testé uma referência a essa sequência alocada estaticamente. &strnão pode ser modificado, por exemplo,

let mut word = "hello world";
word[0] = 's';
word.push('\n');

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

Aperion
fonte
0

Em palavras fáceis, o Stringtipo 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 Stringem algum lugar da pilha.

&strnão faz nenhuma alocação em tempo de execução. Portanto, por motivos de memória, você pode usar &strmais String. Mas lembre-se de que, ao usar, &strvocê pode ter que lidar com vidas explícitas.

00imvj00
fonte
1
em algum lugar na pilha - isso não é completamente preciso.
Shepmaster
O que eu quis dizer é que stré viewde já presente Stringno heap.
precisa saber é o seguinte
1
Entendo que foi isso que você quis dizer e estou dizendo que não é completamente preciso. A "pilha" não é uma parte necessária da declaração.
Shepmaster
-1

Para pessoas em C # e Java:

  • Ferrugem ' String===StringBuilder
  • Rust &str === (imutável) string

Eu gosto de pensar em &strcomo uma exibição em uma string, como uma string interna em Java / C # onde você não pode alterá-la, apenas crie uma nova.

Esquilo
fonte
1
A maior diferença entre as strings Java / C # e as strings Rust é que o Rust garante que a string seja unicode correta, assim, obter o terceiro caractere em uma string requer mais reflexão do que apenas "abc" [2]. (Dado que vivemos em um mundo multi-lingual, isso é uma coisa boa.)
Squirrel
Isto está incorreto . O tópico da mutabilidade já foi abordado na resposta mais votada; por favor leia para saber mais.
Shepmaster # 28/19
-5

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 strcomo um tipo emprestado por meio de uma exibição de fatia de sequência, como &str.

Considerações de uso:

Prefira Stringse você deseja possuir ou alterar uma string - como passar a string para outro thread, etc.

Prefira &strse você deseja ter uma exibição somente leitura de uma sequência.

Desenvolvedor
fonte
Isto está incorreto . O tópico da mutabilidade já foi abordado na resposta mais votada; por favor leia para saber mais.
Shepmaster # 28/19