Propriedade, escopo e movimentações

RustBeginner
Pratique Agora

Introdução

Rust usa propriedade para decidir qual parte de um programa é responsável por um valor e quando esse valor pode ser liberado. Pense na propriedade como responsabilidade e permissão, não como um rótulo permanente ligado a um único nome de variável: essa responsabilidade pode ser transferida para uma nova associação. Isso evita vários erros de memória sem exigir um coletor de lixo.

Neste laboratório, você verá primeiro que um inteiro pequeno pode ser copiado. Em seguida, você pedirá intencionalmente que Rust use uma String pertencente ao programa depois que a propriedade dela tiver sido movida, lerá o diagnóstico resultante do compilador e corrigirá o fluxo de dados usando a propriedade devolvida por uma função. A estrutura do código já está preparada, portanto cada alteração será pequena e focada.

Observe o escopo e um inteiro copiado

Nesta etapa, você copiará um inteiro pequeno para um escopo interno e confirmará que a associação original continua utilizável.

Entre no projeto preparado e abra o arquivo-fonte:

cd /home/labex/project/ownership-demo
nano src/main.rs

Um escopo é a região em que uma associação é válida. As chaves criam um novo escopo interno. A associação local_count existe somente entre o par interno de chaves, enquanto warehouse_count pertence ao escopo externo de main.

Na linha da Etapa 1, substitua somente 0 por warehouse_count:

        let local_count = warehouse_count; // Step 1: replace the placeholder.

O tipo inteiro i32 implementa o comportamento Copy de Rust. Atribuí-lo a local_count copia o valor pequeno em vez de invalidar warehouse_count.

Salve com Ctrl+O, pressione Enter e saia com Ctrl+X. Execute o programa:

cargo run --quiet

As duas primeiras linhas comprovam que ambas as associações podem ser usadas:

Local count: 3
Warehouse count: 3

Quando o escopo interno termina, local_count deixa de ser válido. A associação externa warehouse_count continua válida até o final de main.

Leia um erro de movimentação intencional

Nesta etapa, você usará intencionalmente uma String depois que a propriedade dela tiver sido movida e, em seguida, interpretará o diagnóstico do compilador Rust. A verificação com falha é esperada e representa o resultado de aprendizagem desta etapa.

String::from("PX-104") cria um texto pertencente ao programa. Diferentemente de um i32 pequeno, uma String gerencia um buffer de texto alocado separadamente, cujo tamanho pode mudar. Rust atribui a responsabilidade por esse buffer a um único proprietário para que ele possa ser liberado exatamente uma vez.

A chamada record_parcel(parcel_id) passa a String por valor. A propriedade é movida da associação parcel_id para o parâmetro da função. A função devolve o valor, e a chamada armazena esse proprietário devolvido em _returned_id. O sublinhado inicial informa a Rust que essa associação temporária não será usada por enquanto.

O valor não é destruído quando é movido. Somente a associação antiga perde a permissão para usá-lo. O caminho da propriedade neste programa é:

parcel_id in main → parcel_id parameter in record_parcel → _returned_id in main

Em todos os pontos, permanece um proprietário atual responsável pelo mesmo texto.

Abra o arquivo:

nano src/main.rs

Encontre a linha abaixo do comentário da Etapa 2 e remova somente o // inicial, para que ela fique assim:

    println!("Original after move: {parcel_id}");

Salve e saia. Antes de executar o próximo comando, preveja o resultado: parcel_id não é mais o proprietário, portanto o compilador deverá rejeitar esse uso.

Execute a verificação. É esperado que ela falhe:

cargo check

Encontre estas partes estáveis do diagnóstico:

error[E0382]: borrow of moved value: `parcel_id`
...
value moved here
...
value borrowed here after move

A palavra borrow aparece porque println! precisa apenas de acesso temporário para exibir o valor. Esse acesso continua impossível por meio de parcel_id, porque a propriedade já foi movida. Na próxima etapa, você usará a associação que recebeu a propriedade de volta da função.

Continue com o proprietário devolvido

Nesta etapa, você corrigirá o erro de movimentação nomeando e usando o valor devolvido por record_parcel.

A assinatura da função fn record_parcel(parcel_id: String) -> String informa que a função recebe a propriedade de uma String e devolve uma String. A expressão final parcel_id transfere a propriedade de volta para quem chamou a função.

Abra o arquivo:

nano src/main.rs

Faça duas pequenas alterações. Primeiro, renomeie _returned_id para returned_id na linha da chamada da função. Isso remove o sublinhado temporário porque você está prestes a usar o valor.

Em seguida, substitua a linha de impressão que causa a falha pela linha abaixo:

    println!("Returned owner: {returned_id}");

A associação antiga parcel_id continua inválida depois da movimentação. Renomear _returned_id para returned_id não recupera a associação antiga; apenas dá um nome normal à associação que já recebeu a propriedade de volta. Agora returned_id é o proprietário atual e, portanto, o nome correto a ser usado.

Salve e saia. Depois, verifique o programa corrigido:

cargo check

Um resultado Finished sem o erro E0382 confirma que o fluxo de propriedade é válido. Execute o programa:

cargo run --quiet

A saída completa deverá ser:

Local count: 3
Warehouse count: 3
Recorded parcel: PX-104
Returned owner: PX-104

No final de main, returned_id sai do escopo e Rust libera a String pertencente a ele. Ainda existe apenas um proprietário responsável por esses dados.

Resumo

Você relacionou o escopo léxico ao tempo de vida dos valores, viu uma cópia de i32 sem invalidar sua associação original, acompanhou a movimentação de uma String para uma função, interpretou o E0382 e continuou com segurança usando a propriedade devolvida ao chamador.