image

Access unlimited bootcamps and 650+ courses

50
%OFF
Article image
Franklyn Sancho
Franklyn Sancho02/06/2023 15:00
Share

Rust - um resumo sobre escopo e ownership

    Durante a minha jornada no aprendizado dessa linguagem, jornada essa que perdura até os dias de hoje, desenvolvi diversos programinhas, alguns são bem simples, mas capazes de apresentar a sintaxe e muitas de suas ferramentas.

    Como estou desenvolvendo alguns artigos sobre o assunto, quero saber se vocês se interessam, pois assim posso adicionar diversos tutoriais e passo a passo por aqui. Infelizmente, vejo que não há tanto material sobre rust em português.

    Como já estamos aqui, vou falar um pouquinho sobre essa linguagem e trazer alguns exemplos de sintaxe.

    Sobre o Rust

    Já adianto que o Rust não é uma linguagem simples, tem uma estrutura muito semelhante ao C++, tendo como vantagem o seu gerenciamento de memória, conhecido como Ownership, sendo esta a característica que torna o rust uma linguagem única.

    O Ownership permite um gerenciamento de memória seguro e eficiente, sem necessitar de um garbage collector ou alocamento explicito. A sua abordagem é feita por um sistema de posse que verifica algumas regras.

    Rust usa uma terceira abordagem: a memória é gerenciada através de um sistema de posse, que tem um conjunto de regras verificadas em tempo de compilação. Nenhuma característica relacionada ao ownership implica qualquer custo em tempo de execução.

    São três as regras verificadas:

    1. Cada valor em Rust possui uma variável que é dita seu owner (sua dona).
    2. Pode apenas haver um owner por vez.
    3. Quando o owner sai fora de escopo, o valor será destruído.

    Para quem não tem muita experiência com programação, isso pode parecer um bicho de sete cabeças, mas com exemplos, essa abordagem se torna muito mais simples de compreender.

    Escopo ou Bloco

    Antes de qualquer coisa, precisamos compreender o que é um bloco ou escopo. Geralmente, mas essa não é uma regra geral, é tudo o que está dentro de chaves {}

    fn main() {  
    //este é um bloco/escopo
    }
    

    No código acima nós temos o bloco da função main.

    fn main() {
    //este é um bloco/escopo
    if {
      //este é outro bloco/escopo
    } 
    }
    

    Neste exemplo nós temos dois blocos dentro da função main: o primeiro é o escopo global da função e o segundo da condicional if.

    static mut VARIAVEL_GLOBAL = 1 //O rust só permite fazer isso se o modo unsafe estiver ativado, ou seja, não é uma boa prática. 
    fn main() {
    //este é um bloco/escopo
    if {
        //este é outro bloco/escopo
    }
    }
    

    No exemplo acima nós temos o escopo global da aplicação, recebendo a VARIAVEL_GLOBAL (o que não é uma boa prática na linguagem rust), o escopo da função main e o escopo da condicional.

    Ownership

    Para melhorar a didática, vamos comparar o gerenciamento das variáveis do Rust com o JavaScript: a segunda nos permite criar variáveis globais que serão reconhecidas em todo o código, enquanto o rust funciona de maneira diferente. Vejam os exemplos abaixo:

    Obs. o rust até nos permite criar variáveis globais, mas será necessário ativar o modo unsafe.
    {
    
    let x = 5
    // a variável x será válida dentro desse bloco de chaves
    
    } // aqui ela não é mais válida (fora do bloco)
    

    Vamos a outro exemplo:

    fn main() {
      let mut x = 10;
      if x > 10 {
          return println!("isso é verdadeiro");
      }
      else {
          return println!("{}", x)
      }
    }
    

    Aqui nós temos uma função main, recebendo uma variável x que armazena 10, se o valor de x for maior do que 10, ela retorna “isso é verdadeiro”, caso contrário, retorna o valor de x.

    Nessa estrutura o valor de x será reconhecido dentro dos blocos condicionais. Veja que se o código cair na segunda condição, seu valor será retornado.

    fn exemplo() {
      let x = 5;
    }
    fn main() {
      if x > 4 
          return println!("isso é verdadeiro");
      }
      else {
          return println!("{}", x)
      }
    }
    

    Nesse exemplo nós temos uma função exemplo, recebendo a variável x, e a função main, recebendo uma condicional que trabalha com a variável x. Se tentarmos compilar esse código, vai retornar o seguinte erro: cannot find value `x` in this scope, ou seja, a variável x não foi encontrada no escopo.

    fn main() {
      let x = 10;
      if x > 4 {
          let y = 2;
          println!("isso é verdadeiro");
      } else {
          return println!("{}", y);
      }
    }
    

    Nesse exemplo nós colocamos a variável x de volta na função main e adicionamos uma variável y no bloco da condicional if. Se a condição for verdadeira, o programa retorna “isso é verdadeiro”, se for falso, retorna o valor de y.

    Esse código vai funcionar ou não? Se você respondeu “não”, PARABÉNS! porque o programa vai dar um erro, dizendo que a variável y não foi encontrada no escopo.

    Movendo as variáveis

    Existem algumas maneiras de mover as variáveis, mas vamos fazer pelo uso de parâmetros, veja este exemplo:

    fn main() {
      let x = 5; //temos uma variável x recebendo 5
      teste_condicao(x) //função teste_condição recebendo como parâmetro o valor de x
    }
    //vamos criar a função teste_condição que tem como parâmetro "valor", recebendo como tipo inteiro de 32 bits. 
    teste_condição(valor: i32) {
     if valor < 10 {
          println!("condição falsa")
      } else {
          println!("condição falsa, valor de x é {}: ", valor)
      }
    }
    

    Nesse exemplo nós temos uma função main que tem uma variável x, recebendo 5, e movemos essa variável para o parâmetro “valor” da função teste_condição, ou seja, o parâmetro “valor” da função teste_condição vai passar a valer 5 (o valor da variável x da função main).

    Perceba que a única condição que a função teste_condição tem para o parâmetro “valor” é que ele recebe um inteiro de 32 bits. Se fosse uma string, o programa daria um erro.

    Uma forma de visualizar melhor essa movimentação é desenhar o código dessa forma abaixo:

    fn main() {
      let x = 5; //temos uma variável x recebendo 5
      //agora nós adicionamos "valor: 5", ou seja, parâmetro "valor" recebe a variável x
      teste_condicao(valor: x) 
    }
    //vamos criar a função teste_condição que tem como parâmetro "valor", recebendo como tipo inteiro de 32 bits. 
    teste_condição(valor: i32) {
     if valor < 10 {
          println!("condição falsa")
      } else {
          println!("condição falsa, o valor de x é {}: ", valor)
      }
    }
    

    Pronto, transferimos uma variável de um escopo para outro.

    Conclusão

    Este foi um simples exemplo de como funciona o comportamento das variáveis nos escopos de um programa em rust. Existem muitos outros conceitos interessantes sobre o Ownership, as quais podemos falar futuramente.

    Nos próximos artigos, ao invés de falar apenas sobre a teoria, trarei exemplos de programas reais para que a gente possa ver a construção passo a passo. Prometo que todos os meus projetos estarão dentro do padrão e seguindo todas as convenções.

    Share
    Comments (2)
    André Bezerra
    André Bezerra - 03/06/2023 08:51

    Para o pessoal de Web / Mobile não é um conteúdo tão atrativo, mas para o pessoal de engenharia é MEGA importante ter um debate sobre as tecnologias de ponta. ^^ Muito bom abordar Rust em uma plataforma de ensino online e abrir esse leque de informação de forma democrática.

    Dione Sousa
    Dione Sousa - 02/06/2023 18:10

    Muito interessante, parabéns!! Por favor continue dando dicas de Rust, conteúdo extremamente necessário.