O C ++ possui três maneiras de passar parâmetros para uma função: por valor, por referência lvalue e por referência rvalue. Destes, a passagem por valor cria propriedade no sentido em que a função chamada recebe sua própria cópia, e a passagem por referência de valor indica que o valor pode ser consumido, ou seja, não será mais usado pelo chamador. Passar por referência lvalue significa que o objeto é emprestado temporariamente do chamador.
No entanto, esses tendem a ser "por convenção" e nem sempre podem ser verificados pelo compilador. E você pode acidentalmente transformar uma referência lvalue em uma referência rvalue usando std::move()
. Concretamente, existem três problemas:
Uma referência pode sobreviver ao objeto que faz referência. O sistema de vida útil da Rust impede isso.
Pode haver mais de uma referência mutável / não-const ativa a qualquer momento. O verificador de empréstimos da Rust impede isso.
Você não pode optar por não receber referências. Você não pode ver em um site de chamada se essa função cria uma referência ao seu objeto, sem conhecer a assinatura da função chamada. Portanto, você não pode impedir referências de maneira confiável, nem excluindo métodos especiais de suas classes nem auditando o site de chamada para verificar se está em conformidade com algum guia de estilo "sem referência".
O problema da vida é sobre segurança básica da memória. Obviamente, é ilegal usar uma referência quando o objeto referenciado expirou. Mas é muito fácil esquecer a vida útil quando você armazena uma referência em um objeto, principalmente quando esse objeto sobrevive ao escopo atual. O sistema do tipo C ++ não pode dar conta disso, porque não modela a vida útil do objeto.
O std::weak_ptr
ponteiro inteligente codifica a semântica de propriedade semelhante a uma referência simples, mas requer que o objeto referenciado seja gerenciado por meio de a shared_ptr
, isto é, é contado por referência. Esta não é uma abstração de custo zero.
Embora o C ++ tenha um sistema const, isso não rastreia se um objeto pode ser modificado, mas rastreia se um objeto pode ser modificado por meio dessa referência específica . Isso não fornece garantias suficientes para "concorrência sem medo". Por outro lado, Rust garante que, se houver uma referência mutável ativa que seja a única referência (“Eu sou o único que pode alterar esse objeto”) e se houver referências não mutáveis, todas as referências ao objeto serão não mutáveis ("Enquanto eu posso ler o objeto, ninguém pode alterá-lo").
No C ++, você pode ficar tentado a proteger o acesso a um objeto por meio de um ponteiro inteligente com um mutex. Mas, como discutido acima, uma vez que temos uma referência, ele pode escapar da vida útil esperada. Portanto, esse ponteiro inteligente não pode garantir que é o único ponto de acesso ao seu objeto gerenciado. Esse esquema pode realmente funcionar na prática porque a maioria dos programadores não deseja sabotar a si mesmos, mas, do ponto de vista do sistema de tipos, isso ainda é completamente doentio.
O problema geral dos ponteiros inteligentes é que eles são bibliotecas sobre a linguagem principal. O conjunto de recursos da linguagem principal permite esses ponteiros inteligentes, por exemplo, std::unique_ptr
precisa de construtores de movimento. Mas eles não podem corrigir deficiências no idioma principal. A capacidade de criar referências implicitamente ao chamar uma função e ter referências pendentes juntas significa que a linguagem C ++ principal é incorreta. A incapacidade de limitar referências mutáveis a uma única significa que o C ++ não pode garantir segurança contra condições de corrida com qualquer tipo de simultaneidade.
É claro que em muitos aspectos, C ++ e Rust são mais parecidos do que parecidos, em particular no que diz respeito a seus conceitos de vida útil de objetos determinados estaticamente. Porém, embora seja possível escrever programas C ++ corretos (desde que nenhum dos programadores cometa erros), o Rust garante a correção em relação às propriedades discutidas.