Este tutorial repasa la sintaxis más utilizada en Solidity y demuestra su equivalente en Rust.
Si deseas una visión general de alto nivel sobre las diferencias entre Rust vs Solidity, por favor consulta el tutorial enlazado. Este tutorial asume que ya conoces Solidity, así que por favor consulta nuestro tutorial de Solidity gratuito si no estás familiarizado con Solidity.
Crea un nuevo proyecto de Solana Anchor llamado tryrust y configura el entorno.
Sentencias condicionales
Podemos decir que hay 2 formas en que los desarrolladores pueden controlar el flujo de ejecución en base a una condición específica en Solidity:
- Sentencias If-Else
- Operador ternario
Ahora veamos lo anterior en Solidity, y sus traducciones a Solana.
Sentencias If-Else
En Solidity:
function ageChecker(uint256 age) public pure returns (string memory) {
if (age >= 18) {
return "You are 18 years old or above";
} else {
return "You are below 18 years old";
}
}
En Solana, añade una nueva función llamada age_checker en lib.rs:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
if age >= 18 {
msg!("You are 18 years old or above");
} else {
msg!("You are below 18 years old");
}
Ok(())
}
Ten en cuenta que la condición age >= 18 no tiene paréntesis — estos son opcionales para las sentencias if.
Para probar, añade otro bloque it en ./tests/tryrust.ts:
it("Age checker", async () => {
// Add your test here.
const tx = await program.methods.ageChecker(new anchor.BN(35)).rpc();
console.log("Your transaction signature", tx);
});
Deberíamos tener los siguientes registros después de ejecutar la prueba:
Transaction executed in slot 77791:
Signature: 2Av18ej2YjkRhzybbccPpwEtkw73VcBpDPZgC9iKrmf6mvwbqjA517garhrntWxKAM1ULL2eAv5vDWJ3SjnFZq6j
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: AgeChecker
Program log: You are 18 years old or above
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 440 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Operador ternario
Asignar una sentencia if-else a una variable en Solidity:
function ageChecker(uint256 age) public pure returns (bool a) {
a = age % 2 == 0 ? true : false;
}
Para hacer esto en Solana, básicamente solo asignamos una sentencia if-else a una variable. El programa de Solana a continuación es el mismo que el anterior:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
let result = if age >= 18 {"You are 18 years old or above"} else { "You are below 18 years old" };
msg!("{:?}", result);
Ok(())
}
Ten en cuenta que en el ejemplo del Operador Ternario en Rust, el bloque if/else termina con un punto y coma, ya que esto se está asignando a una variable.
También nota que los valores internos no tienen un punto y coma al final porque se están retornando como el valor de retorno a la variable, de manera similar a cómo no pones un punto y coma después de Ok(()) ya que es una expresión y no una declaración.
El programa registra true si age es par, de lo contrario false:
Transaction executed in slot 102358:
Signature: 2zohZKhY56rLb7myFs8kabdwULJALENyvyFS5LC6yLM264BnkwsThMnotHNAssJbQEzQpmK4yd3ozs3zhG3GH1Gx
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: AgeChecker
Program log: true
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 792 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Rust tiene una estructura de control de flujo aún más poderosa llamada match. Veamos un ejemplo del uso de match a continuación:
pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
match age {
1 => {
// Code block executed if age equals 1
msg!("The age is 1");
},
2 | 3 => {
// Code block executed if age equals 2 or 3
msg!("The age is either 2 or 3");
},
4..=6 => {
// Code block executed if age is in the
// range 4 to 6 (inclusive)
msg!("The age is between 4 and 6");
},
_ => {
// Code block executed for any other age
msg!("The age is something else");
}
}
Ok(())
}
Bucles For
Como sabemos, el bucle for permite iterar sobre rangos, colecciones y otros objetos iterables y se escribe en Solidity de esta manera:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i++) {
// do something...
}
}
Este es el equivalente en Solana (Rust):
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in 0..10 {
// do something...
}
Ok(())
}
Sí, es así de simple, pero ¿cómo iteramos sobre un rango con un paso personalizado? Aquí está el comportamiento previsto en Solidity:
function loopOverSmth() public {
for (uint256 i=0; i < 10; i+=2) {
// do something...
// Increment i by 2
}
}
Aquí está el equivalente en Solana usando step_by:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
for i in (0..10).step_by(2) {
// do something...
msg!("{}", i);
}
Ok(())
}
Al ejecutar la prueba, deberíamos tener los siguientes registros:
Transaction executed in slot 126442:
Signature: 3BSPA11TZVSbF8krjMnge1fgwNsL9odknD2twAsDeYEF39AzaJy1c5TmFCt6LEzLtvWnjzx7VyFKJ4VT1KQBpiwm
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: 0
Program log: 2
Program log: 4
Program log: 6
Program log: 8
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2830 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Arrays y Vectores
Rust difiere de Solidity en cuanto al soporte de arrays. Mientras que Solidity tiene soporte nativo tanto para arrays fijos como dinámicos, Rust solo tiene soporte incorporado para arrays fijos. Si deseas una lista de longitud dinámica, usa un vector.
Ahora, veamos algunos ejemplos que demuestran cómo declarar e inicializar tanto arrays fijos como dinámicos.
Array fijo
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Declare an array of u32 with a fixed size of 5
let my_array: [u32; 5] = [10, 20, 30, 40, 50];
// Accessing elements of the array
let first_element = my_array[0];
let third_element = my_array[2];
// Declare a mutable array of u32 with a fixed size of 3
let mut mutable_array: [u32; 3] = [100, 200, 300];
// Change the second element from 200 to 250
mutable_array[1] = 250;
// Rest of your program's logic
Ok(())
}
Array dinámico
Un método para simular un array dinámico en Solana implica utilizar un Vec (Vector) de la biblioteca estándar de Rust. Aquí tienes un ejemplo a continuación:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Declare a dynamic array-like structure using Vec
let mut dynamic_array: Vec<u32> = Vec::new();
// Add elements to the dynamic array
dynamic_array.push(10);
dynamic_array.push(20);
dynamic_array.push(30);
// Accessing elements of the dynamic array
let first_element = dynamic_array[0];
let third_element = dynamic_array[2];
// Rest of your program's logic
msg!("Third element = {}", third_element);
Ok(())
}
La variable dynamic_array debe ser declarada como mutable (mut) para permitir su mutación (push, pop, sobrescribir en un índice, etc.).
El programa debería registrar esto después de ejecutar la prueba:
Transaction executed in slot 195373:
Signature: 4113irrcBsFbNaiZia5c84yfJpS4Hn4H1QawfUSHYoPuuQPj22JnVFtDMHmZDFkQ3vK15SrDUSTakh5fT4N8UVRf
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: Third element = 30
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 1010 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Mappings
A diferencia de Solidity, Solana carece de una estructura de datos de mapping incorporada. Sin embargo, podemos replicar la funcionalidad de los mappings clave-valor en Solana utilizando el tipo HashMap de la biblioteca estándar de Rust. A diferencia de las cadenas EVM, el mapa que estamos demostrando aquí está en memoria, no en almacenamiento. Las cadenas EVM no tienen mapas hash (hash maps) en memoria. Demostraremos los mappings en almacenamiento para Solana más adelante.
Veamos cómo usar HashMap para crear un mapping en Solana. Copia y pega el fragmento de código proporcionado en el archivo lib.rs, y recuerda reemplazar el program ID con el tuyo:
use anchor_lang::prelude::*;
declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");
#[program]
pub mod tryrust {
use super::*;
// Import HashMap library
use std::collections::HashMap;
pub fn initialize(ctx: Context<Initialize>, key: String, value: String) -> Result<()> {
// Initialize the mapping
let mut my_map = HashMap::new();
// Add a key-value pair to the mapping
my_map.insert(key.to_string(), value.to_string());
// Log the value corresponding to a key from the mapping
msg!("My name is {}", my_map[&key]);
Ok(())
}
}
La variable my_map también se hace mutable para que podamos editarla (es decir, añadir/eliminar pares key → value). ¿También notaste cómo importamos la biblioteca HashMap?
Dado que la función initialize recibe dos parámetros, la prueba también necesita ser actualizada:
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize("name", "Bob").rpc();
console.log("Your transaction signature", tx);
});
Cuando ejecutamos la prueba, vemos el siguiente registro:
Transaction executed in slot 216142:
Signature: 5m4Cx26jaYT3c6YeJbLMDHppvki4Kmu3zTDMgk8Tao9v8b9sH7WgejETzymnHuUfr4hY25opptqniBuwDpncbnB9
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: My name is Bob
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2634 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
Structs
En Solidity y Solana, los structs se utilizan para definir estructuras de datos personalizadas que pueden contener múltiples campos. Veamos un ejemplo de struct tanto en Solidity como en Solana.
En Solidity:
contract SolidityStructs {
// Defining a struct in Solidity
struct Person {
string my_name;
uint256 my_age;
}
// Creating an instance of the struct
Person person1;
function initPerson1(string memory name, uint256 age) public {
// Accessing and modifying struct fields
person1.my_name = name;
person1.my_age = age;
}
}
La correspondencia 1 a 1 en Solana:
pub fn initialize(_ctx: Context<Initialize>, name: String, age: u64) -> Result<()> {
// Defining a struct in Solana
struct Person {
my_name: String,
my_age: u64,
}
// Creating an instance of the struct
let mut person1: Person = Person {
my_name: name,
my_age: age,
};
msg!("{} is {} years old", person1.my_name, person1.my_age);
// Accessing and modifying struct fields
person1.my_name = "Bob".to_string();
person1.my_age = 18;
msg!("{} is {} years old", person1.my_name, person1.my_age);
Ok(())
}
Ejercicio: actualiza el archivo de prueba para pasar dos argumentos Alice y 20 a la función initialize y ejecuta la prueba, deberías obtener los siguientes registros:
Transaction executed in slot 324406:
Signature: 2XBQKJLpkJbVuuonqzirN9CK5dNKnuu5NqNCGTGgQovWBfrdjRcVeckDmqtzyEPe4PP8xSN8vf2STNxWygE4BPZN
Status: Ok
Log Messages:
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
Program log: Instruction: Initialize
Program log: Alice is 20 years old
Program log: Bob is 18 years old
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2601 of 200000 compute units
Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success
En el fragmento de código proporcionado, la implementación de Solidity guarda la instancia de un struct en el almacenamiento (storage), mientras que en la implementación de Solana, todo ocurrió en la función initialize y nada se almacenó en la cadena (on-chain). El almacenamiento se discutirá en un tutorial posterior.
Constantes en Rust
Declarar una variable constante en Rust es sencillo. En lugar de usar la palabra clave let, usa la palabra clave const. Estas se pueden declarar fuera del bloque #[program].
use anchor_lang::prelude::*;
declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");
// *** CONSTANT DECLARED HERE ***
const MEANING_OF_LIFE_AND_EXISTENCE: u64 = 42;
#[program]
pub mod tryrust {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!(&format!("Answer to the ultimate question: {}", MEANING_OF_LIFE_AND_EXISTENCE)); // new line here
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
El tipo usize y el casting (conversión de tipos)
La mayor parte del tiempo podemos asumir que los enteros sin signo son del tipo u64 en Solana, pero hay una excepción al medir la longitud de una lista: será del tipo usize. Necesitarás hacer un cast (conversión) de la variable como demuestra el siguiente código en Rust:
use anchor_lang::prelude::*;
declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");
#[program]
pub mod usize_example {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let mut dynamic_array: Vec<u32> = Vec::from([1,2,3,4,5,6]);
let len = dynamic_array.len(); // this has type usize
let another_var: u64 = 5; // this has type u64
let len_plus_another_var = len as u64 + another_var;
msg!("The result is {}", len_plus_another_var);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
Try Catch
Rust no tiene try catch. Se espera que los fallos devuelvan errores (como hicimos en nuestro tutorial sobre reverts y errores en Solana) o hagan panic para errores no recuperables.
Ejercicio: Escribe un programa en Solana / Rust que tome un vector de u64, lo recorra en un bucle, y añada todos los números pares a otro vector utilizando push, y luego imprima el nuevo vector.
Aprende más con RareSkills
Este tutorial es parte de nuestro curso de Solana gratuito.
Publicado originalmente el 13 de febrero de 2024