Rust oferece aos programadores uma maneira de escrever software seguro para memória sem coleta de lixo, rodando na velocidade nativa da máquina. É também uma linguagem complexa de dominar, com uma curva de aprendizado inicial bastante acentuada. Aqui estão cinco pegadinhas, obstáculos e armadilhas a serem observadas quando você estiver se firmando no Rust – e também para desenvolvedores mais experientes do Rust.
Dicas sobre Rust: 6 coisas que você precisa saber sobre como escrever código Rust
- Você não pode 'desligar' o verificador de empréstimo
- Não use '
_
'para variáveis que você deseja vincular - Os encerramentos não têm as mesmas regras de vida útil que as funções
- Os destruidores nem sempre são executados quando um empréstimo expira
- Cuidado com coisas inseguras e vidas ilimitadas
.unwrap()
renuncia ao controle de tratamento de erros
Você não pode 'desligar' o verificador de empréstimo
Propriedade, empréstimos e vidas úteis estão incluídos na Ferrugem. Eles são parte integrante de como a linguagem mantém a segurança da memória sem coleta de lixo.
Algumas outras linguagens oferecem ferramentas de verificação de código que alertam o desenvolvedor sobre problemas de segurança ou de memória, mas ainda permitem que o código seja compilado. A ferrugem não funciona assim. O verificador de empréstimo—a parte do compilador do Rust que verifica se todas as operações de propriedade são válidas—não é um utilitário opcional que pode ser desativado. O código que não é válido para o verificador de empréstimo não será compilado, ponto final.
Um artigo inteiro poderia (e talvez devesse) ser escrito sobre como não lutar contra o verificador de empréstimo. Vale a pena revisar a seção Rust por exemplo sobre escopo para ver como as regras funcionam para muitos comportamentos comuns.
Nos estágios iniciais de sua jornada no Rust, lembre-se de que você sempre pode solucionar problemas de propriedade fazendo cópias com .clone()
. Para partes do programa que não exigem muito desempenho, fazer cópias raramente terá qualquer impacto mensurável. Então, você pode se concentrar nas partes que fazer precisa de desempenho máximo de cópia zero e descubra como tornar seus empréstimos e vidas úteis mais eficientes nessas partes do programa.
Não use '_' para variáveis que você deseja vincular
O nome da variável _
—um único sublinhado—tem um comportamento especial no Rust. Isso significa que o valor recebido na variável não está vinculado a ela. Normalmente é usado para receber valores que devem ser descartados imediatamente. Se algo emite um must_use
aviso, por exemplo, atribuindo-o a _
é uma maneira típica de silenciar esse aviso.
Para esse fim, não use o sublinhado para nenhum valor que persista além da instrução em que é usado. Observe que estamos falando aqui sobre o declaraçãonão o escopo.
Os cenários a serem observados são aqueles em que você deseja que algo seja mantido até sair do escopo. Se você tiver um bloco de código como
let _ = String::from(" Hello World ").trim();
a string criada será imediatamente sairá do escopo após essa declaração — ela não será mantida até o final do bloco. (A chamada do método serve para garantir que os resultados não sejam eliminados na compilação.)
A maneira fácil de evitar essa pegadinha é apenas use nomes como _user
ou _item
para atribuições que você deseja persistir até o final do escopo, mas não planeja usar para muito mais.
Os encerramentos não têm as mesmas regras de vida útil que as funções
Considere esta função:
fn function(x: &i32) -> &i32 {
x
}
Você pode tentar expressar esta função como um encerramento, para um valor de retorno de uma função:
fn main() {
let closure = |x: &i32| x;
}
O único problema é que não funciona. O compilador irá gritar com o erro: lifetime may not live long enough
porque a entrada e a saída do encerramento têm tempos de vida diferentes.
Uma maneira de contornar isso seria usar uma referência estática:
fn main() {
let _closure: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;
}
Usar uma função separada é mais detalhado, mas evita esse tipo de problema. Isso torna os escopos mais claros e fáceis de analisar visualmente.
Os destruidores nem sempre são executados quando um empréstimo expira
Assim como o C++, o Rust permite criar destruidores para tipos, que podem ser executados quando um objeto sai do escopo. Mas isso não significa que eles sejam garantido para correr.
Isto também é verdade, talvez duplamente, quando um empréstimo expira sobre um determinado objeto. Se um empréstimo expirar em alguma coisa, isso não significa que seu destruidor já tenha sido executado. Na verdade, há momentos em que você não quer que o destruidor seja executado só porque um empréstimo expirou — por exemplo, quando você está segurando um ponteiro para alguma coisa.
A documentação do Rust tem diretrizes para garantir a execução de um destruidor e para saber quando um destruidor terá execução garantida.
Cuidado com coisas inseguras e vidas ilimitadas
A palavra-chave unsafe
existe para marcar o código Rust que pode fazer coisas como desreferenciar ponteiros brutos. É o tipo de coisa que você não precisa fazer com frequência no Rust (esperamos!), mas quando o faz, agora você tem um mundo totalmente novo de problemas em potencial.
Por exemplo, desreferenciar um ponteiro bruto produzido por um unsafe
a operação resulta em uma vida útil ilimitada. Rustonomicon, o livro sobre ferrugem insegura, adverte que uma vida ilimitada “torna-se tão grande quanto o contexto exige”. Isso significa que pode crescer inesperadamente além do que você originalmente precisava ou pretendia.
Se você for escrupuloso sobre o que faz com uma referência ilimitada, não deverá ter problemas. Mas, por segurança, é melhor colocar ponteiros desreferenciados em uma função e usar tempos de vida no limite da função, em vez de deixá-los se movimentar dentro do escopo de uma função.
.unwrap() desiste do controle de tratamento de erros
Sempre que uma operação retorna um Result
, existem duas maneiras básicas de lidar com isso. Um está com .unwrap()
ou um de seus primos (como .unwrap_or()
). O outro está totalmente desenvolvido match
declaração para lidar com um Err
resultado.
.unwrap()
A grande vantagem de é que é conveniente. Se você estiver em um caminho de código onde você nunca espera que uma condição de erro surja, ou onde seria impossível lidar com uma condição de erro de qualquer maneira, você pode usar .unwrap()
para obter o valor que você precisa e cuidar de seus negócios.
Tudo isso tem um custo: qualquer condição de erro causará pânico e interromperá o programa. Os pânicos no Rust são irrecuperáveis por um motivo: são um sinal de que algo está errado o suficiente para indicar um bug real no programa.
Se você usar .unwrap()
ou um dos .unwrap()
variantes de, como .unwrap_or()
, esteja ciente de que você ainda tem capacidade limitada de tratamento de erros. Você deve transmitir algum tipo de valor que esteja em conformidade com o tipo que OK
valor produziria. Com match
você tem muito mais flexibilidade de comportamento do que apenas produzir algo do tipo adequado.
Se você acha que nunca precisará dessa flexibilidade em um determinado caminho do programa, .unwrap()
esta bem. Ainda é recomendado, porém, tentar escrever um texto completo match
primeiro para ver se você negligenciou algum aspecto do manuseio.