e
quer mais?
Me paga um café! :) PIX consultoria@carlosdelfino.eti.br
Curta o post no final da página, use o Disqus, compartilhe em sua rede social. Isso me ajuda e motiva
Obrigado.
Se você pretende desenvolver smart contracts na Solana (ou qualquer projeto de sistemas de alta performance), precisa dominar os fundamentos de Rust. Este tutorial ensina os conceitos essenciais da linguagem usando exemplos práticos e progressivos, preparando você para acompanhar tutoriais avançados como o CriptoSensor na Solana.
1. Instalação e Primeiro Programa
# Instalar Rust via rustup (Linux/macOS/WSL)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verificar a instalação
rustc --version
cargo --version
# Criar um novo projeto
cargo new rust_basico
cd rust_basico
O arquivo src/main.rs já vem com o clássico “Hello, world!”:
fn main() {
println!("Olá, Rust!");
}
Para compilar e executar:
cargo run
Nota:
println!não é uma função — é uma macro (note o!). Macros são expandidas em tempo de compilação e permitem sintaxe que funções comuns não suportam, como formatação com{}. Veremos mais sobre macros na seção 16.
2. Variáveis e Mutabilidade
Em Rust, variáveis são imutáveis por padrão. Isso evita bugs causados por modificações acidentais.
fn main() {
let x = 10; // imutável
// x = 20; // ❌ ERRO: cannot assign twice to immutable variable
let mut y = 10; // mutável (precisa de `mut`)
y = 20; // ✅ OK
println!("y = {}", y);
}
2.1 Shadowing
Você pode redeclarar uma variável com let, mesmo mudando o tipo:
fn main() {
let valor = "42"; // &str
let valor = valor.len(); // agora é usize — shadowing
println!("Tamanho: {}", valor);
}
2.2 Por que isso importa em Smart Contracts?
Em contratos como o CriptoSensor, mutabilidade é controlada explicitamente:
// Somente com &mut podemos modificar o estado da conta
let sensor = &mut ctx.accounts.sensor_account;
sensor.score = 0; // ✅ permitido porque sensor é &mut
sensor.is_active = true;
Sem o mut, o compilador impede a modificação — um mecanismo de segurança crucial para smart contracts.
3. Tipos Primitivos
Rust é fortemente tipado. Conheça os tipos que aparecem frequentemente em smart contracts:
3.1 Inteiros
| Tipo | Tamanho | Faixa | Uso típico |
|---|---|---|---|
u8 |
8 bits | 0 a 255 | Flags, níveis de penalidade |
u64 |
64 bits | 0 a ~18×10¹⁸ | Valores monetários, scores |
i64 |
64 bits | -9×10¹⁸ a 9×10¹⁸ | Timestamps, coordenadas |
u128 |
128 bits | 0 a ~3.4×10³⁸ | Cálculos intermediários |
usize |
Depende da plataforma | — | Índices, tamanhos |
fn main() {
let penalty_level: u8 = 2;
let score: u64 = 150_000; // underscores para legibilidade
let timestamp: i64 = 1_700_000_000;
let calculo: u128 = 999_999_999_999;
let tamanho: usize = 32;
println!("Penalty: {}, Score: {}", penalty_level, score);
}
3.2 Booleanos e Strings
fn main() {
// Booleano
let is_active: bool = true;
// String (heap-allocated, mutável, owned)
let sensor_id: String = String::from("temp-001");
// &str (referência a string, imutável)
let tipo: &str = "temperature";
println!("Sensor {} ({}) ativo: {}", sensor_id, tipo, is_active);
}
3.3 String vs &str
Esta distinção é fundamental em Rust:
| Aspecto | String |
&str |
|---|---|---|
| Alocação | Heap (dinâmica) | Referência (emprestada) |
| Ownership | Tem dono | Emprestada |
| Mutabilidade | Pode crescer/shrink | Imutável |
| Em structs | ✅ Comum | Requer lifetimes |
fn main() {
let owned: String = "hello".to_string(); // String → owned
let borrowed: &str = &owned; // &str → emprestada
let literal: &str = "world"; // &str → literal (estático)
// Convertendo
let s1: String = literal.to_string();
let s2: String = String::from(literal);
let bytes: &[u8] = literal.as_bytes(); // &str → &[u8]
println!("{} {} {:?}", owned, literal, bytes);
}
Em smart contracts, campos de structs usam String porque a struct é dona do dado:
pub struct SensorAccount {
pub sensor_id: String, // String porque a conta é dona do dado
pub sensor_type: String,
}
4. Constantes
Constantes são sempre imutáveis e devem ter tipo explícito:
/// Tamanho máximo do identificador do sensor
const MAX_SENSOR_ID_LEN: usize = 32;
/// Tamanho máximo da descrição do tipo de sensor
const MAX_SENSOR_TYPE_LEN: usize = 16;
/// Fator de escala para aritmética de ponto fixo
const SCALE: u64 = 1_000_000;
/// Base em basis points (100% = 10_000)
const BPS_BASE: u64 = 10_000;
fn main() {
println!("Escala: {}, BPS: {}", SCALE, BPS_BASE);
}
Diferenças entre const e let:
const: avaliada em tempo de compilação, sempre imutável, tipo obrigatório, escopo global ou local.let: avaliada em tempo de execução, pode sermut, tipo inferido, escopo local.
5. Funções
5.1 Sintaxe Básica
/// Soma dois valores u64 e retorna o resultado.
fn somar(a: u64, b: u64) -> u64 {
a + b // sem ponto-e-vírgula = expressão de retorno
}
/// Função sem retorno (retorna implicitamente `()`)
fn imprimir_score(nome: &str, score: u64) {
println!("Score de {}: {}", nome, score);
}
fn main() {
let total = somar(100, 50);
imprimir_score("temp-001", total);
}
5.2 Retornando Result
Em smart contracts, funções retornam Result<()> para indicar sucesso ou erro:
fn validar_sensor_id(sensor_id: &str) -> Result<(), String> {
if sensor_id.len() > 32 {
Err("Identificador do sensor excede o tamanho máximo".to_string())
} else if sensor_id.is_empty() {
Err("Identificador não pode ser vazio".to_string())
} else {
Ok(())
}
}
fn main() {
match validar_sensor_id("temp-001") {
Ok(()) => println!("Sensor válido!"),
Err(msg) => println!("Erro: {}", msg),
}
}
5.3 Visibilidade com pub
// Sem `pub` → privada ao módulo
fn funcao_interna() {}
// Com `pub` → acessível fora do módulo
pub fn funcao_publica() {}
Em smart contracts, as instruções (funções que o usuário pode chamar) são pub fn:
pub fn register_sensor(
ctx: Context<RegisterSensor>,
sensor_id: String,
sensor_type: String,
latitude: i64,
longitude: i64,
) -> Result<()> {
// ... implementação
Ok(())
}
6. Structs (Estruturas de Dados)
Structs são o equivalente a classes (sem herança) em Rust. São a principal forma de organizar dados.
6.1 Definição e Instanciação
struct SensorAccount {
owner: String,
sensor_id: String,
sensor_type: String,
latitude: i64,
longitude: i64,
score: u64,
penalty_level: u8,
is_active: bool,
registered_at: i64,
}
fn main() {
let sensor = SensorAccount {
owner: "Alice".to_string(),
sensor_id: "temp-001".to_string(),
sensor_type: "temperature".to_string(),
latitude: -38_516_700,
longitude: -386_120_000,
score: 0,
penalty_level: 0,
is_active: true,
registered_at: 1_700_000_000,
};
println!("Sensor: {} ({})", sensor.sensor_id, sensor.sensor_type);
println!("Localização: {}, {}", sensor.latitude, sensor.longitude);
println!("Ativo: {}", sensor.is_active);
}
6.2 Campos Públicos
Em smart contracts, os campos são pub para permitir acesso externo:
pub struct OracleAccount {
pub authority: String,
pub brl_usdc_rate: u64,
pub token_price: u64,
pub last_updated: i64,
pub twap_observations: Vec<PriceObservation>,
pub bump: u8,
}
6.3 Métodos com impl
Você pode adicionar métodos a uma struct usando impl:
struct Sensor {
sensor_id: String,
score: u64,
penalty_level: u8,
is_active: bool,
}
impl Sensor {
/// Construtor (convenção: `new`)
fn new(id: &str) -> Self {
Sensor {
sensor_id: id.to_string(),
score: 0,
penalty_level: 0,
is_active: true,
}
}
/// Método que recebe &self (leitura)
fn esta_banido(&self) -> bool {
self.penalty_level >= 3
}
/// Método que recebe &mut self (escrita)
fn adicionar_score(&mut self, delta: u64) {
if self.is_active && !self.esta_banido() {
self.score += delta;
}
}
/// Aplicar penalidade com slashing
fn aplicar_penalidade(&mut self, nivel: u8, slash: u64) {
self.penalty_level = nivel;
self.score = self.score.saturating_sub(slash);
if nivel >= 3 {
self.is_active = false;
}
}
}
fn main() {
let mut s = Sensor::new("temp-001");
s.adicionar_score(100);
println!("Score: {}", s.score); // 100
s.aplicar_penalidade(1, 30);
println!("Score após penalidade: {}", s.score); // 70
s.aplicar_penalidade(3, 0);
println!("Banido: {}", s.esta_banido()); // true
println!("Ativo: {}", s.is_active); // false
}
6.4 &self vs &mut self
| Assinatura | Significado | Quando usar |
|---|---|---|
&self |
Referência imutável | Apenas leitura |
&mut self |
Referência mutável | Modifica o estado |
self |
Toma ownership | Consome a struct |
7. Enums (Enumerações)
Enums definem um tipo que pode ser uma dentre várias variantes.
7.1 Enum Simples
enum StatusSensor {
Ativo,
Inativo,
Banido,
}
fn descrever_status(status: &StatusSensor) -> &str {
match status {
StatusSensor::Ativo => "Operando normalmente",
StatusSensor::Inativo => "Desativado temporariamente",
StatusSensor::Banido => "Removido da rede permanentemente",
}
}
fn main() {
let status = StatusSensor::Ativo;
println!("{}", descrever_status(&status));
}
7.2 Enum com Dados
enum Erro {
SensorIdMuitoLongo,
ScoreOverflow(u64),
Personalizado(String),
}
fn descrever_erro(e: &Erro) -> String {
match e {
Erro::SensorIdMuitoLongo => "ID muito longo".to_string(),
Erro::ScoreOverflow(valor) => format!("Overflow no score: {}", valor),
Erro::Personalizado(msg) => msg.clone(),
}
}
fn main() {
let e = Erro::ScoreOverflow(999);
println!("{}", descrever_erro(&e));
}
7.3 Enums de Erro em Smart Contracts
No framework Anchor, enums de erro seguem um padrão com atributos:
#[error_code]
pub enum RegistryError {
#[msg("Identificador do sensor excede o tamanho máximo")]
SensorIdTooLong,
#[msg("Sensor está inativo")]
SensorInactive,
#[msg("Sensor está banido da rede")]
SensorBanned,
#[msg("Nível de penalidade inválido")]
InvalidPenalty,
#[msg("Overflow aritmético")]
Overflow,
#[msg("Acesso não autorizado")]
Unauthorized,
}
Cada variante representa um erro distinto com uma mensagem legível.
8. Result e Option — Tratamento de Erros
Rust não tem exceções. Em vez disso, usa os tipos Result<T, E> e Option<T>.
8.1 Result
// Result é definido assim na standard library:
// enum Result<T, E> {
// Ok(T),
// Err(E),
// }
fn dividir(a: u64, b: u64) -> Result<u64, String> {
if b == 0 {
Err("Divisão por zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
// Tratando com match
match dividir(100, 3) {
Ok(resultado) => println!("100 / 3 = {}", resultado),
Err(msg) => println!("Erro: {}", msg),
}
// Tratando com unwrap (cuidado: panic se for Err)
let r = dividir(100, 5).unwrap();
println!("100 / 5 = {}", r);
}
8.2 O Operador ?
O ? propaga erros automaticamente — se a expressão for Err, a função retorna imediatamente com esse erro:
fn calcular_tokens(receita: u64, preco: u64) -> Result<u64, String> {
if preco == 0 {
return Err("Preço não pode ser zero".to_string());
}
let elegivel = receita
.checked_mul(950_000)
.ok_or("Overflow na multiplicação".to_string())?; // ← propaga se None
let tokens = elegivel
.checked_div(preco)
.ok_or("Overflow na divisão".to_string())?; // ← propaga se None
Ok(tokens)
}
fn main() {
match calcular_tokens(534_440_000, 500_000) {
Ok(t) => println!("Tokens: {}", t),
Err(e) => println!("Erro: {}", e),
}
}
8.3 Option
// Option é definido assim:
// enum Option<T> {
// Some(T),
// None,
// }
fn buscar_sensor(id: &str) -> Option<String> {
match id {
"temp-001" => Some("Sensor de Temperatura".to_string()),
"umid-002" => Some("Sensor de Umidade".to_string()),
_ => None,
}
}
fn main() {
// Tratando com match
match buscar_sensor("temp-001") {
Some(nome) => println!("Encontrado: {}", nome),
None => println!("Sensor não encontrado"),
}
// Tratando com if let
if let Some(nome) = buscar_sensor("xyz") {
println!("Encontrado: {}", nome);
} else {
println!("Não encontrado");
}
// Convertendo Option para Result com ok_or
let resultado: Result<String, &str> = buscar_sensor("xyz")
.ok_or("Sensor não existe");
println!("{:?}", resultado); // Err("Sensor não existe")
}
8.4 ok_or — Ponte entre Option e Result
O método .ok_or() converte Option<T> em Result<T, E>, e é extremamente usado em smart contracts:
fn main() {
let a: u64 = 100;
let b: u64 = 50;
// checked_add retorna Option<u64>
// ok_or converte para Result<u64, &str>
let soma = a.checked_add(b)
.ok_or("Overflow aritmético");
println!("{:?}", soma); // Ok(150)
// Com valor que causaria overflow em u8
let x: u8 = 250;
let resultado = x.checked_add(10)
.ok_or("Overflow aritmético");
println!("{:?}", resultado); // Err("Overflow aritmético")
}
9. Ownership, Borrowing e Referências
Este é o conceito mais importante de Rust. O sistema de ownership garante segurança de memória sem garbage collector.
9.1 Ownership (Posse)
Cada valor em Rust tem exatamente um dono. Quando o dono sai do escopo, o valor é descartado.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 foi MOVIDO para s2
// println!("{}", s1); // ❌ ERRO: s1 não é mais válido
println!("{}", s2); // ✅ OK
}
9.2 Borrowing (Empréstimo)
Para usar um valor sem tomar posse, usamos referências:
fn imprimir_sensor(id: &String) { // empréstimo imutável
println!("Sensor: {}", id);
}
fn ativar_sensor(ativo: &mut bool) { // empréstimo mutável
*ativo = true; // desreferencia para modificar
}
fn main() {
let sensor_id = String::from("temp-001");
imprimir_sensor(&sensor_id); // empresta sem mover
println!("Ainda tenho: {}", sensor_id); // ✅ sensor_id ainda é válido
let mut is_active = false;
ativar_sensor(&mut is_active);
println!("Ativo: {}", is_active); // true
}
9.3 Regras de Borrowing
- Você pode ter múltiplas referências imutáveis (
&T) ao mesmo tempo. - Você pode ter apenas uma referência mutável (
&mut T) por vez. - Não pode ter referências imutáveis e mutáveis ao mesmo tempo.
fn main() {
let mut score = 100u64;
// Múltiplas referências imutáveis: OK
let r1 = &score;
let r2 = &score;
println!("{} {}", r1, r2);
// Uma referência mutável: OK (r1 e r2 já não são usadas)
let r3 = &mut score;
*r3 += 50;
println!("{}", r3); // 150
}
9.4 Borrowing em Smart Contracts
Em smart contracts Solana/Anchor, o padrão &mut ctx.accounts.conta é a forma de obter uma referência mutável a uma conta para modificá-la:
// Referência mutável → podemos escrever na conta
let sensor = &mut ctx.accounts.sensor_account;
sensor.score = sensor.score.checked_add(delta_score)
.ok_or(RegistryError::Overflow)?;
// Referência imutável → apenas leitura
let oracle = &ctx.accounts.oracle_account;
let rate = oracle.brl_usdc_rate;
10. Vetores (Vec)
Vec<T> é uma coleção dinâmica, como ArrayList em Java ou listas em Python.
fn main() {
// Criando um vetor
let mut observacoes: Vec<u64> = Vec::new();
// Adicionando elementos
observacoes.push(172_400);
observacoes.push(175_000);
observacoes.push(170_800);
// Acessando
println!("Primeira: {}", observacoes[0]);
println!("Tamanho: {}", observacoes.len());
// Removendo o primeiro elemento
if observacoes.len() >= 3 {
observacoes.remove(0);
}
println!("Após remove: {:?}", observacoes);
// Iterando
let mut soma: u128 = 0;
for obs in &observacoes {
soma += *obs as u128;
}
println!("Soma: {}", soma);
}
10.1 Vec com Structs
#[derive(Debug, Clone)]
struct PriceObservation {
price: u64,
duration: u64,
timestamp: i64,
}
const MAX_OBSERVATIONS: usize = 24;
fn main() {
let mut observations: Vec<PriceObservation> = Vec::new();
// Adicionando observações
observations.push(PriceObservation {
price: 500_000,
duration: 3600,
timestamp: 1_700_000_000,
});
observations.push(PriceObservation {
price: 510_000,
duration: 3600,
timestamp: 1_700_003_600,
});
// Limitando o tamanho (janela deslizante)
if observations.len() >= MAX_OBSERVATIONS {
observations.remove(0);
}
// Calculando TWAP (Time-Weighted Average Price)
let mut weighted_sum: u128 = 0;
let mut total_duration: u128 = 0;
for obs in &observations {
weighted_sum += (obs.price as u128) * (obs.duration as u128);
total_duration += obs.duration as u128;
}
if total_duration > 0 {
let twap = (weighted_sum / total_duration) as u64;
println!("TWAP: {}", twap);
}
}
11. Módulos e Organização de Código
11.1 Módulos Inline
mod registry {
pub struct Sensor {
pub id: String,
pub score: u64,
}
impl Sensor {
pub fn new(id: &str) -> Self {
Sensor {
id: id.to_string(),
score: 0,
}
}
}
// Função privada (sem pub)
fn validar_interno(id: &str) -> bool {
id.len() <= 32
}
// Função pública que usa a privada
pub fn criar_sensor(id: &str) -> Result<Sensor, String> {
if validar_interno(id) {
Ok(Sensor::new(id))
} else {
Err("ID muito longo".to_string())
}
}
}
fn main() {
let sensor = registry::criar_sensor("temp-001").unwrap();
println!("Sensor: {} (score: {})", sensor.id, sensor.score);
}
11.2 use e super
mod protocolo {
pub const SCALE: u64 = 1_000_000;
pub mod oracle {
use super::SCALE; // acessa constante do módulo pai
pub fn converter_brl_para_usdc(brl: u64, taxa: u64) -> u64 {
((brl as u128) * (taxa as u128) / (SCALE as u128)) as u64
}
}
pub mod treasury {
use super::SCALE;
pub fn calcular_partição(receita: u64, bps: u64) -> u64 {
((receita as u128) * (bps as u128) / (10_000u128)) as u64
}
}
}
fn main() {
use protocolo::oracle;
use protocolo::treasury;
let usdc = oracle::converter_brl_para_usdc(5_000_000_000, 172_400);
println!("5000 BRL = {} USDC (×10⁶)", usdc);
let operacional = treasury::calcular_partição(usdc, 4000);
println!("Operação (40%): {}", operacional);
}
11.3 use crate::* e use super::*
Em smart contracts Anchor, é muito comum ver:
#[program]
pub mod criptosensor_registry {
use super::*; // importa tudo que está acima no arquivo (structs, consts, etc.)
pub fn register_sensor(ctx: Context<RegisterSensor>, sensor_id: String) -> Result<()> {
// pode acessar MAX_SENSOR_ID_LEN, RegistryError, etc.
Ok(())
}
}
12. Traits e Derive
Traits são como interfaces em outras linguagens — definem comportamento compartilhado.
12.1 Traits Básicas
trait Pontuavel {
fn score(&self) -> u64;
fn esta_ativo(&self) -> bool;
// Método com implementação padrão
fn pode_receber_tokens(&self) -> bool {
self.esta_ativo() && self.score() > 0
}
}
struct Sensor {
id: String,
score: u64,
is_active: bool,
}
struct EdgeNode {
id: String,
score: u64,
is_active: bool,
}
impl Pontuavel for Sensor {
fn score(&self) -> u64 { self.score }
fn esta_ativo(&self) -> bool { self.is_active }
}
impl Pontuavel for EdgeNode {
fn score(&self) -> u64 { self.score }
fn esta_ativo(&self) -> bool { self.is_active }
}
fn imprimir_elegibilidade(item: &dyn Pontuavel) {
println!("Score: {} | Pode receber: {}", item.score(), item.pode_receber_tokens());
}
fn main() {
let sensor = Sensor { id: "temp-001".to_string(), score: 120, is_active: true };
let node = EdgeNode { id: "edge-A".to_string(), score: 0, is_active: true };
imprimir_elegibilidade(&sensor); // Score: 120 | Pode receber: true
imprimir_elegibilidade(&node); // Score: 0 | Pode receber: false
}
12.2 Derive — Implementação Automática de Traits
O atributo #[derive(...)] gera implementações automaticamente:
#[derive(Debug, Clone, PartialEq)]
struct PriceObservation {
price: u64,
duration: u64,
timestamp: i64,
}
fn main() {
let obs1 = PriceObservation { price: 500_000, duration: 3600, timestamp: 1_700_000_000 };
let obs2 = obs1.clone(); // Clone
println!("{:?}", obs1); // Debug
println!("Iguais: {}", obs1 == obs2); // PartialEq
}
Traits comuns usadas com derive:
| Trait | O que faz |
|---|---|
Debug |
Permite {:?} no println |
Clone |
Permite .clone() |
Copy |
Cópia implícita (tipos simples) |
PartialEq |
Permite == e != |
Default |
Valores padrão com Default::default() |
12.3 Derive em Smart Contracts
No Anchor, structs de dados usam derives específicos:
// Structs de dados (contas on-chain)
#[account]
#[derive(InitSpace)]
pub struct SensorAccount {
pub owner: Pubkey,
#[max_len(32)]
pub sensor_id: String,
pub score: u64,
pub bump: u8,
}
// Structs auxiliares (dentro de contas)
#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)]
pub struct PriceObservation {
pub price: u64,
pub duration: u64,
pub timestamp: i64,
}
13. Generics (Tipos Genéricos)
Generics permitem escrever código que funciona com múltiplos tipos.
13.1 Funções Genéricas
use std::fmt::Display;
fn imprimir_valor<T: Display>(nome: &str, valor: T) {
println!("{}: {}", nome, valor);
}
fn maior<T: PartialOrd>(a: T, b: T) -> T {
if a >= b { a } else { b }
}
fn main() {
imprimir_valor("Score", 150u64);
imprimir_valor("Sensor", "temp-001");
println!("Maior: {}", maior(100u64, 200u64));
}
13.2 Structs Genéricas
#[derive(Debug)]
struct Resultado<T> {
valor: T,
epoch: u64,
timestamp: i64,
}
fn main() {
let r1: Resultado<u64> = Resultado { valor: 862_000_000, epoch: 10, timestamp: 1_700_000_000 };
let r2: Resultado<String> = Resultado { valor: "sucesso".to_string(), epoch: 10, timestamp: 1_700_000_000 };
println!("{:?}", r1);
println!("{:?}", r2);
}
13.3 Generics em Smart Contracts
O Anchor usa generics extensivamente. Os mais comuns:
// Context<T> — contexto da instrução parametrizado pela struct de contas
pub fn register_sensor(ctx: Context<RegisterSensor>) -> Result<()> { ... }
// Account<'info, T> — conta tipada
pub sensor_account: Account<'info, SensorAccount>,
// Program<'info, T> — referência a um programa
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
14. Lifetimes (Tempos de Vida)
Lifetimes garantem que referências nunca apontem para dados que já foram liberados.
14.1 Conceito Básico
// O lifetime 'a diz: "a referência retornada vive pelo menos
// tanto quanto as referências de entrada"
fn mais_longo<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() { s1 } else { s2 }
}
fn main() {
let s1 = String::from("temperatura");
let resultado;
{
let s2 = String::from("umidade");
resultado = mais_longo(&s1, &s2);
println!("Mais longo: {}", resultado); // OK: s2 ainda existe aqui
}
// Não podemos usar `resultado` aqui se ele referenciasse s2
}
14.2 Lifetimes em Structs
// A struct contém uma referência → precisa de lifetime
struct ConfigView<'a> {
nome: &'a str,
versao: &'a str,
}
fn main() {
let nome = String::from("CriptoSensor");
let versao = String::from("1.0.0");
let view = ConfigView {
nome: &nome,
versao: &versao,
};
println!("{} v{}", view.nome, view.versao);
}
14.3 O Lifetime 'info em Smart Contracts
No Anchor, 'info é o lifetime das referências de conta da Solana. Cada struct de contexto o utiliza:
#[derive(Accounts)]
pub struct RegisterSensor<'info> {
#[account(mut)]
pub owner: Signer<'info>,
#[account(init, payer = owner, space = 8 + SensorAccount::INIT_SPACE,
seeds = [b"sensor", owner.key().as_ref(), sensor_id.as_bytes()],
bump
)]
pub sensor_account: Account<'info, SensorAccount>,
pub system_program: Program<'info, System>,
}
O 'info indica que todas as referências de conta vivem durante a execução da instrução.
15. Type Casting e Aritmética Segura
15.1 Casting com as
fn main() {
let a: u64 = 534_440_000;
let b: u64 = 951_230;
// Promove para u128 para evitar overflow no cálculo intermediário
let resultado = (a as u128) * (b as u128) / (1_000_000u128);
let final_u64 = resultado as u64; // volta para u64
println!("Resultado: {}", final_u64);
}
Cuidado: casting com as pode truncar valores silenciosamente! Use sempre com cuidado:
fn main() {
let grande: u128 = 999_999_999_999_999;
let truncado = grande as u64; // OK (cabe em u64)
let muito_grande: u128 = u128::MAX;
let perigoso = muito_grande as u64; // ⚠️ Truncado silenciosamente!
println!("Truncado: {}", truncado);
println!("Perigoso: {}", perigoso);
}
15.2 Aritmética Segura com checked_*
Em smart contracts, nunca use operações aritméticas comuns (+, -, *, /) com valores de usuário. Use as variantes checked_*:
fn main() {
let score: u64 = 100;
let delta: u64 = 50;
// checked_add: retorna None se houver overflow
let novo_score = score.checked_add(delta);
println!("checked_add: {:?}", novo_score); // Some(150)
// checked_mul: retorna None se houver overflow
let receita: u64 = 534_440_000;
let bps: u64 = 4000;
let operacional = receita.checked_mul(bps);
println!("checked_mul: {:?}", operacional); // Some(2_137_760_000_000)
// checked_div: retorna None se divisor for zero
let por_epoch = receita.checked_div(0);
println!("checked_div por 0: {:?}", por_epoch); // None
// checked_sub: retorna None se resultado for negativo (para unsigned)
let penalidade: u64 = 200;
let resultado = score.checked_sub(penalidade);
println!("checked_sub: {:?}", resultado); // None (100 - 200 < 0)
}
15.3 saturating_sub — Subtração Segura
Quando você quer que o resultado seja no mínimo zero (sem panic nem None):
fn main() {
let score: u64 = 100;
// saturating_sub: resultado mínimo = 0 (não estoura para negativo)
let resultado = score.saturating_sub(200);
println!("saturating_sub(100, 200) = {}", resultado); // 0
let resultado2 = score.saturating_sub(30);
println!("saturating_sub(100, 30) = {}", resultado2); // 70
}
15.4 Method Chaining com checked_* e ok_or
O padrão mais comum em smart contracts combina aritmética segura com tratamento de erros:
fn calcular_partição(receita: u64, bps: u64) -> Result<u64, String> {
let resultado = receita
.checked_mul(bps) // Option<u64>
.ok_or("Overflow na multiplicação".to_string())? // Result<u64, String>
.checked_div(10_000) // Option<u64>
.ok_or("Divisão por zero".to_string())?; // Result<u64, String>
Ok(resultado)
}
fn pipeline_completo(receita: u64, taxa: u64, preco: u64) -> Result<u64, String> {
// Encadeamento completo: receita × taxa / SCALE / preço
let tokens = (receita as u128)
.checked_mul(taxa as u128)
.ok_or("Overflow em receita × taxa".to_string())?
.checked_div(1_000_000)
.ok_or("Divisão por escala falhou".to_string())?
.checked_div(preco as u128)
.ok_or("Divisão por preço falhou".to_string())?;
Ok(tokens as u64)
}
fn main() {
match calcular_partição(862_000_000, 5000) {
Ok(v) => println!("Incentivo (50%): {} USDC", v),
Err(e) => println!("Erro: {}", e),
}
match pipeline_completo(534_440_000, 951_230, 500_000) {
Ok(t) => println!("Tokens: {}", t),
Err(e) => println!("Erro: {}", e),
}
}
16. Macros
Macros são identificadas pelo ! e são expandidas em tempo de compilação.
16.1 Macros da Standard Library
fn main() {
// println! — imprime com formatação
let sensor_id = "temp-001";
let score = 150u64;
println!("Sensor {} | Score: {}", sensor_id, score);
// format! — retorna String formatada (sem imprimir)
let msg = format!("Score do sensor {} atualizado: +{} → total {}", sensor_id, 50, score);
println!("{}", msg);
// vec! — cria Vec com valores iniciais
let precos: Vec<u64> = vec![500_000, 510_000, 505_000];
println!("{:?}", precos);
// assert! e assert_eq! — verificações (panic se falhar)
assert!(score > 0, "Score deve ser positivo");
assert_eq!(2 + 2, 4, "Matemática quebrou");
}
16.2 Macros do Framework Anchor
Em smart contracts Anchor, macros especiais são essenciais:
// declare_id! — define o endereço do programa
declare_id!("REGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
// msg! — registra mensagem nos logs da transação (equivale a println! on-chain)
msg!("Sensor registrado: {}", sensor.sensor_id);
// require! — aborta a transação se a condição for falsa
require!(sensor_id.len() <= MAX_SENSOR_ID_LEN, RegistryError::SensorIdTooLong);
require!(amount > 0, TreasuryError::ZeroAmount);
// emit! — emite um evento on-chain que pode ser capturado por indexadores
emit!(PaymentEvent {
client: ctx.accounts.client.key(),
amount,
order_id,
epoch: treasury.current_epoch,
timestamp: Clock::get()?.unix_timestamp,
});
17. Atributos
Atributos (#[...]) modificam o comportamento de itens no código.
17.1 Atributos Comuns
// #[derive] — implementa traits automaticamente
#[derive(Debug, Clone, PartialEq)]
struct Ponto {
x: i64,
y: i64,
}
// #[allow] — suprime avisos do compilador
#[allow(dead_code)]
fn funcao_nao_usada() {}
// #[cfg] — compilação condicional
#[cfg(test)]
mod tests {
#[test]
fn test_soma() {
assert_eq!(2 + 2, 4);
}
}
fn main() {
let p = Ponto { x: 10, y: 20 };
println!("{:?}", p);
}
17.2 Atributos Anchor para Smart Contracts
// #[program] — marca o módulo como programa Solana
#[program]
pub mod criptosensor_registry {
// instruções do programa
}
// #[account] — marca struct como conta on-chain (serialização automática)
#[account]
#[derive(InitSpace)]
pub struct SensorAccount {
pub owner: Pubkey,
#[max_len(32)] // #[max_len] — tamanho máximo para cálculo de espaço
pub sensor_id: String,
pub score: u64,
pub bump: u8,
}
// #[derive(Accounts)] — gera validação automática de contas
#[derive(Accounts)]
#[instruction(sensor_id: String)] // acessa argumentos da instrução
pub struct RegisterSensor<'info> {
#[account(mut)] // conta mutável (será modificada)
pub owner: Signer<'info>,
#[account(
init, // será inicializada (criada)
payer = owner, // quem paga o aluguel
space = 8 + SensorAccount::INIT_SPACE, // tamanho da conta
seeds = [b"sensor", owner.key().as_ref(), sensor_id.as_bytes()],
bump // PDA bump seed
)]
pub sensor_account: Account<'info, SensorAccount>,
pub system_program: Program<'info, System>,
}
// #[error_code] — define erros customizados
#[error_code]
pub enum RegistryError {
#[msg("Sensor está inativo")]
SensorInactive,
}
// #[event] — define evento emitido pela instrução
#[event]
pub struct PaymentEvent {
pub client: Pubkey,
pub amount: u64,
pub timestamp: i64,
}
18. Pattern Matching e Controle de Fluxo
18.1 match
fn classificar_penalidade(nivel: u8) -> &'static str {
match nivel {
0 => "Sem penalidade",
1 => "Perda parcial de score",
2 => "Slashing severo",
3 => "Banimento permanente",
_ => "Nível desconhecido", // _ = padrão (wildcard)
}
}
fn main() {
for nivel in 0..=4 {
println!("Nível {}: {}", nivel, classificar_penalidade(nivel));
}
}
18.2 if let — Pattern Matching Simplificado
fn main() {
let resultado: Option<u64> = Some(862_000_000);
// Ao invés de match completo...
if let Some(valor) = resultado {
println!("Receita: {} USDC", valor / 1_000_000);
}
// Equivalente a:
match resultado {
Some(valor) => println!("Receita: {} USDC", valor / 1_000_000),
None => {},
}
}
18.3 Expressões if-else como Valor
Em Rust, if-else é uma expressão que retorna valor:
fn main() {
let epoch: u64 = 0;
let revenue: u64 = 500_000_000;
let ema_anterior: u64 = 0;
// if-else como expressão
let new_ema = if epoch == 0 {
revenue
} else {
let alpha = 333_333u64;
let alpha_r = ((alpha as u128) * (revenue as u128) / 1_000_000) as u64;
let complement = ((666_667u128) * (ema_anterior as u128) / 1_000_000) as u64;
alpha_r + complement
};
println!("EMA: {}", new_ema);
}
18.4 require! como Guard Clause
Em smart contracts, require! funciona como guard clause que aborta a transação:
// Cada require! verifica uma condição antes de prosseguir
// Se falhar, retorna erro imediatamente
// require!(condição, Erro);
// Equivalente a:
// if !condição { return Err(Erro.into()); }
// Exemplos do CriptoSensor:
// require!(sensor_id.len() <= MAX_SENSOR_ID_LEN, RegistryError::SensorIdTooLong);
// require!(amount > 0, TreasuryError::ZeroAmount);
// require!(sensor.is_active, RegistryError::SensorInactive);
// require!(penalty_level >= 1 && penalty_level <= 3, RegistryError::InvalidPenalty);
19. Slices e Referências a Arrays
Slices (&[T]) são referências a uma porção contígua de memória.
fn soma_slice(valores: &[u64]) -> u128 {
let mut total: u128 = 0;
for v in valores {
total += *v as u128;
}
total
}
fn main() {
let precos: Vec<u64> = vec![500_000, 510_000, 505_000, 515_000];
// Slice de todo o vetor
let total = soma_slice(&precos);
println!("Total: {}", total);
// Slice parcial
let ultimos_dois = &precos[2..];
println!("Últimos dois: {:?}", ultimos_dois);
// Byte slices — muito usados em seeds de PDA
let seed: &[u8] = b"sensor"; // byte string literal
let id_bytes: &[u8] = "temp-001".as_bytes(); // String → bytes
println!("Seed: {:?}", seed);
println!("ID bytes: {:?}", id_bytes);
// Slice de slices (usado em signer_seeds)
let bump: u8 = 255;
let seeds: &[&[u8]] = &[b"emission_config".as_ref(), &[bump]];
println!("Seeds: {:?}", seeds);
}
20. Juntando Tudo — Exemplo Completo
Vamos construir um mini-protocolo que usa todos os conceitos aprendidos:
// --- Constantes ---
const SCALE: u64 = 1_000_000;
const MAX_ID_LEN: usize = 32;
const SENSOR_POOL_BPS: u64 = 7000;
const BPS_BASE: u64 = 10_000;
// --- Enums ---
#[derive(Debug, PartialEq)]
enum ProtoError {
IdMuitoLongo,
Overflow,
SensorInativo,
ScoreZero,
}
// --- Structs ---
#[derive(Debug, Clone)]
struct Sensor {
id: String,
score: u64,
is_active: bool,
penalty_level: u8,
}
#[derive(Debug)]
struct EpochResult {
epoch: u64,
total_tokens: u64,
distribuicoes: Vec<(String, u64)>,
}
// --- Traits ---
trait Participante {
fn id(&self) -> &str;
fn score(&self) -> u64;
fn elegivel(&self) -> bool;
}
impl Participante for Sensor {
fn id(&self) -> &str { &self.id }
fn score(&self) -> u64 { self.score }
fn elegivel(&self) -> bool { self.is_active && self.penalty_level < 3 }
}
// --- Implementação ---
impl Sensor {
fn new(id: &str) -> Result<Self, ProtoError> {
if id.len() > MAX_ID_LEN {
return Err(ProtoError::IdMuitoLongo);
}
Ok(Sensor {
id: id.to_string(),
score: 0,
is_active: true,
penalty_level: 0,
})
}
fn adicionar_score(&mut self, delta: u64) -> Result<(), ProtoError> {
if !self.is_active {
return Err(ProtoError::SensorInativo);
}
self.score = self.score.checked_add(delta)
.ok_or(ProtoError::Overflow)?;
Ok(())
}
fn aplicar_penalidade(&mut self, nivel: u8, slash: u64) {
self.penalty_level = nivel;
self.score = self.score.saturating_sub(slash);
if nivel >= 3 {
self.is_active = false;
}
}
}
// --- Funções do Protocolo ---
/// Calcula o fator de decaimento e^(-λ·t) via série de Taylor
fn approximate_exp_neg(x: u64) -> Result<u64, ProtoError> {
if x == 0 { return Ok(SCALE); }
let x_128 = x as u128;
let scale_128 = SCALE as u128;
let x2 = x_128.checked_mul(x_128).ok_or(ProtoError::Overflow)?
.checked_div(scale_128).ok_or(ProtoError::Overflow)?;
let x3 = x2.checked_mul(x_128).ok_or(ProtoError::Overflow)?
.checked_div(scale_128).ok_or(ProtoError::Overflow)?;
let result = scale_128
.checked_sub(x_128).unwrap_or(0)
.checked_add(x2 / 2).ok_or(ProtoError::Overflow)?
.checked_sub(x3 / 6).unwrap_or(0);
Ok(result.min(scale_128) as u64)
}
/// Distribui tokens proporcionalmente ao score dos sensores
fn distribuir_tokens(
sensores: &[Sensor],
tokens_total: u64,
) -> Result<EpochResult, ProtoError> {
// Filtra sensores elegíveis
let elegiveis: Vec<&Sensor> = sensores.iter()
.filter(|s| s.elegivel())
.collect();
let score_total: u64 = elegiveis.iter()
.map(|s| s.score())
.sum();
if score_total == 0 {
return Err(ProtoError::ScoreZero);
}
// Pool de sensores = 70%
let sensor_pool = (tokens_total as u128)
.checked_mul(SENSOR_POOL_BPS as u128).ok_or(ProtoError::Overflow)?
.checked_div(BPS_BASE as u128).ok_or(ProtoError::Overflow)? as u64;
let mut distribuicoes = Vec::new();
for sensor in &elegiveis {
let tokens = (sensor_pool as u128)
.checked_mul(sensor.score() as u128).ok_or(ProtoError::Overflow)?
.checked_div(score_total as u128).ok_or(ProtoError::Overflow)? as u64;
distribuicoes.push((sensor.id().to_string(), tokens));
}
Ok(EpochResult {
epoch: 10,
total_tokens: tokens_total,
distribuicoes,
})
}
// --- Main ---
fn main() {
println!("═══ Mini-Protocolo CriptoSensor ═══\n");
// Criar sensores
let mut sensores = vec![
Sensor::new("temp-001").unwrap(),
Sensor::new("umid-002").unwrap(),
Sensor::new("press-003").unwrap(),
Sensor::new("spam-004").unwrap(),
];
// Adicionar scores
sensores[0].adicionar_score(120).unwrap();
sensores[1].adicionar_score(95).unwrap();
sensores[2].adicionar_score(60).unwrap();
sensores[3].adicionar_score(200).unwrap();
// Banir o spammer
sensores[3].aplicar_penalidade(3, 200);
println!("Sensores:");
for s in &sensores {
println!(" {} | Score: {} | Ativo: {} | Elegível: {}",
s.id, s.score, s.is_active, s.elegivel());
}
// Calcular decaimento para epoch 10
let lambda = 5_000u64; // 0.005 × 10^6
let epoch = 10u64;
let x = lambda * epoch / SCALE;
// x será 0 devido à divisão inteira — usamos o cálculo correto:
let x_correto = (lambda as u128 * epoch as u128 / (SCALE as u128 / 1000)) as u64;
// Simplificando: x = 5000 * 10 = 50_000
let x_final = lambda.checked_mul(epoch).unwrap();
let decay = approximate_exp_neg(x_final).unwrap();
println!("\nDecaimento e^(-λ·{}) = {}/{} ≈ {:.6}",
epoch, decay, SCALE, decay as f64 / SCALE as f64);
// Tokens do epoch (simulação)
let incentive_share: u64 = 534_440_000;
let eligible = (incentive_share as u128 * decay as u128 / SCALE as u128) as u64;
let token_price: u64 = 500_000;
let tokens = (eligible as u128 * SCALE as u128 / token_price as u128) as u64;
println!("Incentive share: {} USDC (×10⁶)", incentive_share);
println!("Receita elegível: {} USDC (×10⁶)", eligible);
println!("Tokens emitidos: {} (×10⁶)", tokens);
// Distribuir
match distribuir_tokens(&sensores, tokens) {
Ok(result) => {
println!("\n═══ Distribuição Epoch {} ═══", result.epoch);
println!("Pool sensores (70%): {}", tokens * 7 / 10);
for (id, qtd) in &result.distribuicoes {
println!(" {} → {} tokens (×10⁶)", id, qtd);
}
}
Err(e) => println!("Erro: {:?}", e),
}
}
21. Próximos Passos
Com estes conceitos dominados, você está preparado para:
- Ler e entender smart contracts em Rust/Anchor — todos os padrões fundamentais foram cobertos.
- Seguir o tutorial avançado — CriptoSensor na Solana: Smart Contracts em Rust com USDC usa exatamente os conceitos ensinados aqui.
- Explorar o ecossistema — The Rust Book é a referência oficial e gratuita.
Resumo dos Conceitos
| Conceito | Seção | Uso em Smart Contracts |
|---|---|---|
Variáveis e mut |
2 | let sensor = &mut ctx.accounts... |
| Tipos primitivos | 3 | u8, u64, i64, u128, bool, String |
| Constantes | 4 | const SCALE: u64 = 1_000_000 |
Funções e pub |
5 | Instruções do programa |
| Structs | 6 | Contas on-chain (SensorAccount, etc.) |
| Enums | 7 | Erros customizados (RegistryError, etc.) |
Result e ? |
8 | Retorno de instruções, .ok_or() |
| Ownership/Borrowing | 9 | &ctx.accounts vs &mut ctx.accounts |
| Vetores | 10 | Vec<PriceObservation> no oráculo |
| Módulos | 11 | mod criptosensor_registry { use super::* } |
| Traits e Derive | 12 | #[derive(InitSpace)], AnchorSerialize |
| Generics | 13 | Context<T>, Account<'info, T> |
| Lifetimes | 14 | RegisterSensor<'info> |
| Type Casting | 15 | as u128 para cálculos intermediários |
| Aritmética segura | 15 | checked_add, saturating_sub |
| Macros | 16 | msg!, require!, emit! |
| Atributos | 17 | #[account], #[program], #[derive(Accounts)] |
| Pattern Matching | 18 | match, if let, guard clauses |
| Slices | 19 | Seeds de PDA, signer_seeds |
Bons estudos e bom código! 🦀
Não deixe de me pagar um café, faz um PIX: consultoria@carlosdelfino.eti.br de qualquer valor.