Contents

Rust - References e Borrowing

Contents

References e Borrowing

Através do uso de referencias podemos emprestar uma variável para uma função sem ter que lhe transferir a sua propriedade ‘onwer’

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let s1 = String::from("Ola mundo!");            // Linha A1
    imprimir_string_e_tamanho(&s1);                 // Linha A2
    println!("Funcao 1 - String   : {}", s1);       // Linha A3
    println!("Funcao 1 - Tamanho  : {}", s1.len()); // Linha A4
}

fn imprimir_string_e_tamanho(s: &String) {
    println!("Funcao 2 - String   : {}", s);        // Linha B1
    println!("Funcao 2 - Tamanho  : {}", s.len());  // Linha B2
}

No exemplo acima s1 foi criado na ‘Linha A1’. Logo após emprestamos por referencia a variável s1 para a função ‘imprimir_string_e_tamanho’ que por sua vez imprime em ‘Linha B1’ e ‘Linha B2’ os valores da variável.

Note que após a ‘Linha B2’ o escopo da função ‘imprimir_string_e_tamanho’ chega ao fim e as variáveis que pertencem a essa função são desalocadas.

Note também que acessamos a variável s1 na ‘Linha A3’ e na ‘Linha A4’. Isso só é possível por causa do conceito de ‘References’ e ‘Borrowing’. Quando utilizamos a chave ‘&’ estamos dizendo ao compilador RUST que queremos emprestar e não transferir a variável para a função chamada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let s1 = String::from("Ola mundo");
    let s2 = imprimir_string_e_tamanho(s1);
    // s1 é invalidado pois transferimos seu 'owner' na chamada acima
    // porem recebemos o 'owner' de outra variavel que foi entregue pela função
    println!("Funcao 1 - String   : {}", s2);
    println!("Funcao 1 - Tamanho  : {}", s2.len());
}

fn imprimir_string_e_tamanho(s: String) -> String {
    println!("Funcao 2 - String   : {}", s);
    println!("Funcao 2 - Tamanho  : {}", s.len());
    s
}

O exemplo acima demonstra como ter o mesmo resultado quando não utilizamos referencias. Nesse caso estamos passando o ‘onwer’ de ‘s1’ para a função ‘imprimir_string_e_tamanho’ e depois passando o ‘onwer’ de s por retorno para a função ‘main’

Referencias mutáveis

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::From("Ola");
    modificar_referencia(s);
    println!("{}", s);
}

fn modificar_referencia(s: &mut String) {
    s.push_str(" mundo!");
}

Para modificar a referencia tudo o que precisamos fazer é torná-la mutável.

Porem, RUST tem uma regra para esses momentos onde estamos trabalhando com referencias mutáveis.

A regra é:

  • Você só pode possuir uma referência mutável para um endereço de memória por escopo.

Isso significa que:

1
2
3
4
5
fn main() {
    let s = String::from("Ola"); // Linha 01
    let mr1 = &mut s;            // Linha 02
    let mr2 = &mut s;            // Linha 03
}

Na ‘Linha 05’ estamos quebrando essa regra. ‘mr2’ é a segunda referencia mutável para a área de memória apontada por ’s'. Graças a essa regra o nosso código acima não terá sua compilação finalizada e recebera um erro do RUST.

Esta regra introduzida pelo compilador RUST protege o programador de ‘data race’ que acontece quando o cenário abaixo é satisfeito:

  • Dois ou mais ponteiros acessam o mesmo conteúdo apontado pelo endereço de memória.
  • Pelo menos um dos ponteiros esta sendo usado para escrever dados
  • Não existe nenhum mecanismo para sincronizar o acesso ao dado

O cenário acima pode causar um comportamento inesperado no seu programa e momentos como esse são difíceis de debugar.

Um outro problema ocorre quando combinamos referências mutáveis com referências não mutáveis que apontam para o mesmo endereço de memória

1
2
3
4
5
fn main() {
    let s = String::from("Ola");
    let r1 = &s;
    let mr1 = &mut s;
}

Não podemos ter referências mutáveis e não mutáveis trabalhando junto em RUST pois usuários de ‘r1’ esperam que o valor de s nunca mude e para respeitar esse compromisso RUST não pode deixar que ‘mr1’ exista.

Ponteiros que apontam para nada

Em linguagens como C é possível acessar um ponteiro que teve sua área de memória liberada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// codigo em C
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char * aloca_mem();

int main() {
    char *c = aloca_mem();
    printf("%s", c);
}
char * aloca_mem() {
    char *c = malloc(10);
    strncpy(c, "Ola", 4);
    free(c);
    return c;
}

O código acima pode gerar uma interrupção em seu programa ou um comportamento inesperado. O mesmo código em RUST seria:

1
2
3
4
5
6
7
8
9
fn main() {
    let referencia = aloca_mem();
    println!("{}", referencia);
}

fn aloca_mem() -> &String {
    let s = String::from("Ola");
    &s
}

Porem, o código acima retorna um erro de compilação dizendo que o tempo de vida de ’s' já acabou quando esta é retornada por parâmetro para a função main.

Para que a lógica do código acima funcione precisamos transferir a propriedade para evitar que a variável seja desalocada ao fim do escopo de ‘aloca_mem’

1
2
3
4
5
6
7
8
9
fn main() {
    let variavel = aloca_mem();
    println!("{}", variavel);
}

fn aloca_mem() -> String {
    let s = String::from("Ola");
    s
}