Los programas de Solana no imponen una estructura de código base en particular, por lo que la organización del código a menudo depende de la preferencia del desarrollador y de la complejidad del programa. De hecho, un programa de Solana puede existir como un solo archivo lib.rs, como hemos visto hasta ahora en esta serie.
Pero a medida que su programa crece en complejidad, querrá separar la lógica y los datos en archivos contextuales y una estructura de carpetas clara para que el código sea más fácil de ubicar, mantener y extender.
El ecosistema de desarrollo de Solana sigue un patrón común para organizar las diferentes partes de un programa. Este artículo enseña cómo organizar un programa de Solana siguiendo este patrón tanto para Anchor como para programas raw Solana.
Componentes de la estructura de un programa de Solana
Estructura unitaria básica de un programa de Solana
Todo programa de Solana es un crate de biblioteca de Rust Cargo. Eso significa que la estructura predeterminada comienza como un proyecto de Cargo, con Cargo.toml que define las dependencias y la configuración de compilación, y lib.rs que contiene la lógica del programa. Puede generar esta estructura ejecutando el comando cargo init --lib my_program y la estructura del programa generado se mostrará a continuación:
my_program/
├── Cargo.lock
├── Cargo.toml
└── src
└── lib.rs
Para que este sea un programa de Solana, necesitaremos agregar el siguiente código al archivo Cargo.toml.
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
solana-program = "2.0.0"
Esto es lo que significa la configuración anterior:
- La sección
[lib]concrate-type = ["cdylib", "lib"]le dice a Cargo que compile el programa tanto como una biblioteca dinámica (cdylib), que Solana requiere para el despliegue como un archivo.so, y como una biblioteca estándar de Rust (lib) para pruebas locales o para reutilizar la lógica en varios programas. - La dependencia
solana-program = "2.0.0"incorpora los crates del SDK de Solana, que proporcionan acceso a los tipos de tiempo de ejecución de Solana, macros y funciones auxiliares para escribir programas on-chain.
Componentes de un programa de Solana
Para comprender adecuadamente cómo estructurar el código base de un programa de Solana, primero entendamos los componentes lógicos ****que conforman un programa de Solana. Cada programa suele incluir las siguientes partes:
- Punto de entrada (Entry point): define la primera función que llamará el runtime de Solana.
- Instrucciones (Instructions): definen qué acciones puede realizar el programa y cómo se serializan y deserializan los datos de entrada, es decir, cómo se estructuran los argumentos de las funciones.
- Procesamiento de instrucciones (Instruction processing): implementa la lógica que ejecuta cada instrucción; aquí es donde ocurre el cálculo principal.
- Cuentas (Accounts): describen la disposición de los datos on-chain. Cada tipo de cuenta especifica qué estado mantiene y cómo se serializa (comúnmente con el crate
borshdirectamente o con macros deAnchor). También necesitamos definir las cuentas con las que interactuaremos. - Manejo de errores (Error handling): proporciona códigos de error descriptivos para simplificar la depuración y la interpretación de errores en el lado del cliente.
- Pruebas (Tests): verifican que su programa se comporte como se espera cuando se despliega localmente.
Podemos estructurar un programa simple de Solana con archivos que representen los conceptos anteriores como se muestra a continuación. En esta estructura, lib.rs sirve como la raíz del programa; expone módulos y los vincula entre sí. El archivo entrypoint.rs define la función que llama el runtime de Solana cuando se invoca el programa. Esta función entrypoint, convencionalmente llamada process_instruction, implementa un despachador que enruta cada instrucción entrante a su manejador correspondiente (como se discutió en tutoriales anteriores).
Cada archivo restante corresponde a uno de los componentes lógicos descritos anteriormente.
program/
├── src/
│ ├── entrypoint.rs // Program entry point (process_instruction)
│ ├── instruction.rs // Instruction enum and data structures
│ ├── processor.rs // Business logic for each instruction
│ ├── state.rs // Account data structures
│ ├── error.rs // Custom error types
│ └── lib.rs // Module declarations and re-exports
Aunque estos nombres de archivo pueden ser arbitrarios, es convencional usar nombres que se relacionen con el concepto que se está implementando.
La estructura anterior es similar tanto en Anchor como en los programas raw Solana. La principal diferencia es que Anchor usa macros para generar el entrypoint y los procesadores automáticamente, mientras que en los programas raw Solana, usted los define manualmente. Esta estructura funciona para programas simples, pero a medida que su programa crezca, tendrá múltiples instrucciones, procesadores o estados, lo que significa que es posible que necesite organizarlos en carpetas.
Profundicemos en cómo debe organizarse un programa de Solana.
Estructura de proyecto de Anchor
Anchor simplifica el desarrollo de programas de Solana al abstraer la mayor parte del código repetitivo requerido en los programas raw Solana. También proporciona una plantilla de proyecto coherente que soporta todo el flujo de trabajo, desde escribir el programa hasta compilarlo, probarlo y desplegarlo.
A continuación se muestra una estructura típica de un proyecto de Anchor, que ya ha visto varias veces en esta serie.
├── Anchor.toml
├── app
├── Cargo.lock
├── Cargo.toml
├── migrations
│ └── deploy.ts
├── package.json
├── programs
│ ├── hello-program
│ │ ├── Cargo.toml
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── Xargo.toml
│ ├── token_vault
│ ├── Cargo.toml
│ ├── src
│ │ └── lib.rs
│ └── Xargo.toml
├── tests
│ └──hello-program.ts
├── tsconfig.json
└── yarn.lock
Cada parte de esta estructura cumple un rol específico en el flujo de trabajo de Anchor:
- El archivo
Anchor.tomldefine la configuración de compilación y despliegue - La carpeta
migrationsalmacena scripts de despliegue - El directorio
testsincluye pruebas de integración que se ejecutan en un validador local - El directorio
apppuede contener código de cliente que interactúa con el programa desplegado
Observe que el directorio programs en Anchor contiene subdirectorios que reflejan la estructura básica de código repetitivo de Cargo que discutimos anteriormente, la cual era solo para un programa.
Un proyecto de Anchor está estructurado para que pueda trabajar en múltiples programas on-chain de Solana dentro de un solo proyecto. La siguiente ilustración compara un proyecto típico de Rust Cargo con cómo Anchor organiza sus proyectos.

Diferencia entre las estructuras de Anchor y raw Solana
- Anchor genera estos archivos completamente configurados para su compilación inmediata. Puede ejecutar
anchor buildjusto después de crear el proyecto y obtener un binario del programa funcional. - Para la configuración de un programa raw Solana con Cargo, usted configura manualmente el código repetitivo de Cargo como un programa de Solana agregando las dependencias y las configuraciones de tipo de crate a
Cargo.tomlcomo discutimos anteriormente. Y si desea ejecutar múltiples programas en un proyecto, necesita un directorioprograms, al igual que el directorioprogramsde Anchor. Es convencional usar el nombreprogramspara contener directorios de programas, aunque puede usar cualquier nombre, y usted configura cada programa a través de su propio archivoCargo.toml.
Note que los archivos Xargo.toml se encuentran en la estructura de proyecto generada por Anchor.

Son el archivo de configuración predeterminado de Anchor que maneja cómo se compilan los programas de Anchor al bytecode de Extended Berkeley Packet Filter (eBPF). Aprenderemos más sobre esto en la siguiente sección.
Cómo maneja Anchor la compilación cruzada hacia eBPF
El archivo Xargo.toml dentro de cada programa le dice a Rust cómo compilar su código para el entorno blockchain de Solana. Su programa de Solana se ejecuta en la Máquina Virtual de Solana (SVM) en los validadores, la cual ejecuta bytecode de Extended Berkeley Packet Filter (eBPF).
Cuando escribe código Rust en su máquina y lo compila, el compilador de Rust normalmente produce instrucciones para el procesador de su computadora (como x86 o ARM). Pero los validadores de Solana no pueden ejecutar esas instrucciones. Solo entienden el bytecode eBPF.
Este proceso de compilación para una arquitectura diferente se llama compilación cruzada (cross-compilation). El archivo Xargo.toml especifica cómo el compilador de Rust debería compilar su código para eBPF en lugar de para su máquina local. El archivo controla qué partes de la biblioteca estándar de Rust se incluyen en el programa compilado final.
Los programas de Solana tienen límites de tamaño estrictos (10 MB), por lo que la configuración de Xargo.toml asegura que el programa compilado incluya solo lo que su programa necesita. Anchor genera el archivo Xargo.toml automáticamente cuando ejecuta anchor init {project-name} o anchor new {program-name}. No necesitará modificarlo. El contenido del archivo Xargo.toml se ve así:
[target.bpfel-unknown-unknown.dependencies.std]
features = []
El nombre del target bpfel-unknown-unknown es un Rust compilation target triple. Le dice al compilador de Rust para qué tipo de máquina y entorno está compilando. Tiene 3 partes, separadas por guiones. Esto es lo que significa cada parte:
bpfel- compila para la arquitectura BPF con un orden de bytes little-endianunknown- el primer unknown significa, no compilar para ningún SO específicounknown- el último unknown significa, no compilar para ninguna ABI (Interfaz Binaria de Aplicaciones) específica
El arreglo vacío features (features = []) significa que está utilizando una versión mínima de la biblioteca estándar optimizada para el despliegue en blockchain.
Los programas raw Solana manejan la compilación cruzada de manera diferente. Usted compila su programa con cargo-build-sbf, lo que compila su código Rust a bytecode eBPF sin necesidad de un archivo Xargo.toml separado.
Cómo estructura Anchor múltiples programas
Anchor utiliza un workspace de Cargo para gestionar múltiples programas en un solo proyecto. Un workspace le permite trabajar en varios programas relacionados que comparten dependencias y configuraciones de compilación.
Anchor utiliza dos tipos de archivos Cargo.toml con diferentes propósitos. El Cargo.toml raíz define la estructura del workspace y las configuraciones de compilación compartidas. El directorio de cada programa contiene su propio Cargo.toml que declara las dependencias específicas de ese programa.
Incluso si dos programas de Solana usan el mismo crate de Rust, cada uno debe declararlo por separado en el archivo Cargo.toml dentro del directorio del programa. No se puede declarar una dependencia una sola vez a nivel del workspace y hacer que todos los programas la hereden.
Si dos programas en el mismo workspace de Anchor, digamos A y B, dependen del mismo crate, podrían usarlo de manera diferente. El programa A podría usar una función de la biblioteca mientras que el programa B usa cinco. Esto afecta qué cantidad de la biblioteca se puede recortar durante la compilación.
Pero los programas comparten configuraciones de compilación. Las dependencias y configuraciones de compilación del workspace se definen en el archivo Cargo.toml ubicado en la raíz de su proyecto de Anchor, el cual también contiene varias configuraciones predeterminadas como se muestra en la captura de pantalla a continuación.
La sección [workspace] del archivo Cargo.toml en el directorio raíz define un arreglo members que especifica los directorios específicos que forman parte del workspace. Observe que contiene ["programs/"] que es el nombre de su directorio predeterminado de programas de Solana. El archivo Cargo a continuación es un archivo Cargo.toml raíz:

Esto es lo que significa cada sección en el archivo Cargo.toml raíz:
La sección [workspace] define qué programa pertenece al workspace y controla cómo Cargo maneja las dependencias a través de múltiples paquetes relacionados.
members = ["programs/*"]- Cada subdirectorio dentro deprogramspertenece a este workspaceresolver = "2"- Esta configuración le dice a Cargo que use su algoritmo de resolución de dependencias más nuevo (la versión 2)
La sección [profile.release] controla la configuración de compilación cuando se compila en modo release. Permite configurar niveles de optimización, información de depuración y el comportamiento de generación de código
overflow-checks = true- Mantiene habilitadas las comprobaciones de desbordamiento aritmético en las compilaciones release, previniendo errores de desbordamiento de enteros que podrían corromper el estado de su programalto = "fat"- Esta configuración habilita la optimización en tiempo de enlace, que analiza todo el programa durante el enlazado del compilador (un proceso donde el compilador combina todo el código compilado en el ejecutable final) para eliminar el código no utilizado y las funciones en línea, reduciendo el tamaño del binario final. Si no queremos un LTO más rápido pero con menos optimización, podemos establecer el parámetroltoenthincodegen-units = 1- Compila todo su programa en una sola pasada en lugar de dividir el trabajo de compilación a través de múltiples fragmentos paralelos; cuanto mayor sea el valor, en más fragmentos se dividirá la compilación. Establecerlo en1permite al compilador realizar la optimización en todo el código base a la vez, produciendo binarios más pequeños y optimizados, pero a costa de tiempos de compilación más largos. La configuración predeterminada de Rust escodegen-units = 16, lo que acelera las compilaciones pero puede resultar en binarios ligeramente más grandes.
La sección [profile.release.build-override] especifica configuraciones de compilación específicamente para los scripts de compilación, que son programas que se ejecutan antes de que su código principal se compile para generar código o configurar la compilación.
opt-level = 3- Aplica la máxima optimización a los scripts de compilaciónincremental = false- Deshabilita la compilación incremental. Cada compilación se realiza de principio a fin. Esto ralentiza el tiempo de compilación pero reduce el riesgo de que los artefactos sobrantes arruinen el proceso de compilacióncodegen-units = 1- Aplica la misma optimización de una sola unidad a los scripts de compilación
Código generado automáticamente por Anchor
Todo el proyecto de Anchor y los programas individuales dentro de él no incluyen archivos entrypoint.rs y processor.rs explícitos, los cuales son requisitos clave en los proyectos de Solana. Como hemos discutido anteriormente, el atributo #[program] dentro del archivo lib.rs genera automáticamente el código fuente del entrypoint junto con la lógica para manejar la decodificación de instrucciones, el despacho y la deserialización de cuentas (que normalmente estarían en el archivo processor.rs en los programas raw Solana).
El siguiente código muestra cómo se usa el atributo #[program] en los programas de Anchor:
#[program]
pub mod hello_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Program initialized!");
Ok(())
}
}
Mejor organización de proyectos en Anchor 1.0
Anchor 1.0 introduce una disposición de proyecto que fomenta una mejor organización de los archivos dentro de cada programa.
Ejecutar anchor init en Anchor 1.0 generará todos los componentes estándar del proyecto como instrucciones, estado, errores y constantes en módulos separados.
Este cambio ayuda a los nuevos desarrolladores a aprender patrones más limpios para organizar programas de Solana más grandes. Aún puede generar la antigua disposición de un solo archivo usando la bandera --template single (anchor init --template single).
Aquí hay un ejemplo de cómo se verá la nueva estructura predeterminada:
programs
└── vote
├── Cargo.toml
└── src
├── instructions
│ ├── initialize.rs
│ └── mod.rs
├── state
│ └── mod.rs
├── constants.rs
├── error.rs
└── lib.rs
Hay dos elementos nuevos a tener en cuenta: los archivos mod.rs dentro de cada directorio y el módulo de instrucciones initialize.rs. Los discutiremos en una sección posterior.
Con esta nueva estructura, ya no comienza un programa con un solo archivo lib.rs que mezcla cuentas e instrucciones. En su lugar, su estado reside en su propio directorio, y cada instrucción se ubica en su propio módulo.
El ejemplo a continuación muestra el antiguo patrón de un solo archivo que Anchor usaba de forma predeterminada, donde la lógica del programa y los tipos de cuenta se encuentran juntos dentro de lib.rs.
use anchor_lang::prelude::*;
declare_id!("6uAEFiYjmgJhCCqw8JPH8chZRWJPzHFBJYuZFMWaML3w");
#[program]
pub mod program_structure {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
msg!("Greetings from: {:?}", ctx.program_id);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
Estructura estándar de un programa de Solana
En este punto, ha visto lo que hace cada parte de un programa de Solana y cómo Anchor las organiza automáticamente. Pero incluso sin Anchor, puede seguir un diseño modular y coherente que asegure que su programa sea mantenible.
Hemos estudiado cómo algunos de los principales proyectos de Solana estructuran sus programas y notamos un patrón consistente que promueve la colaboración, la mantenibilidad y la escalabilidad. Un programa típico de Solana sigue la estructura a continuación, con directorios para el entrypoint y el procesador definidos explícitamente solo en programas raw Solana.
program/
├── Cargo.toml
└── src/
├── entrypoint.rs
├── instructions/
│ ├── mod.rs
│ ├── initialize.rs
│ └── transfer.rs
├── processor/
│ ├── mod.rs
│ ├── initialize.rs
│ └── transfer.rs
├── state/
│ ├── mod.rs
│ ├── account.rs
│ └── config.rs
├── error.rs
├── utils/
│ ├── mod.rs
│ ├── pda.rs
│ ├── math.rs
│ └── validation.rs
└── lib.rs
Presentamos nuevos archivos: el mod.rs para cada directorio y el initialize.rs en la sección anterior y en la estructura de arriba. Expliquémoslos:
El archivo mod.rs
El archivo mod.rs sirve como el punto de declaración de módulos para un directorio en Rust. Cuando organiza código relacionado en una carpeta como instructions/, Rust no reconoce automáticamente los archivos dentro como parte de su programa. Necesita decirle explícitamente a Rust qué archivos deben ser compilados y hacerlos accesibles a otras partes de su código base, ese es el trabajo de mod.rs (y no es arbitrario, Rust espera exactamente este nombre de archivo cuando se define un módulo a través de un directorio).
Así es como se ve instructions/mod.rs:
pub mod initialize;
pub mod transfer;
Cada línea declara un archivo en el directorio como un módulo. La palabra clave pub hace que estos módulos sean públicamente accesibles fuera del directorio de instrucciones. Sin estas declaraciones, Rust no compilará initialize.rs o transfer.rs, y otras partes de su programa no podrán importar su contenido.
En su archivo lib.rs, luego expone el módulo de instrucciones para que esté disponible en todo su programa:
pub mod instructions;
pub use instructions::*;
Este patrón se repite para cada directorio en su programa.
- El archivo
processor/mod.rsdeclara módulos del procesador - El
state/mod.rsdeclara módulos de estado - y
utils/mod.rsdeclara módulos de utilidad.
Archivo instructions/initialize.rs
La función initialize en un archivo lib.rs de Anchor es una convención de Anchor para configurar el estado inicial de sus programas para sus instrucciones. Podemos reubicar esa inicialización del estado en un archivo diferente para mantener limpio su archivo lib.rs.
Así es como se ve la función initialize generada por defecto en el archivo lib.rs:

Así es como usará la función initialize en su archivo lib.rs para proyectos de Anchor cuando la haya movido a un archivo initialize.rs dedicado y la haya convertido en un módulo en el directorio de instrucciones:

Directorio processors
En raw Solana, processor/ implementa los manejadores de instrucciones. Cada módulo de procesador corresponde a un módulo de instrucción en instructions/.
Estructura de Anchor modularizada
Una estructura de programa estándar al estilo Anchor para programas individuales se vería como la de abajo, sin un entrypoint o un archivo de procesador. Esta estructura refleja la estructura de proyectos de Anchor versión 1.0:
programs/
└── my_program/
├── Cargo.toml
└── src/
├── lib.rs
├── instructions/
│ ├── transfer.rs
│ └── mod.rs
├── state/
│ ├── config.rs
│ ├── account.rs
│ └── mod.rs
├── error.rs
├── utils/
│ ├── mod.rs
│ ├── pda.rs
│ ├── math.rs
│ └── validation.rs
Con esta estructura, se vuelve más fácil para los desarrolladores localizar la lógica relacionada, razonar sobre el flujo del programa y extender la funcionalidad sin romper el comportamiento existente, independientemente de si es un programa raw Solana o en Anchor.
Este artículo es parte de una serie de tutoriales sobre desarrollo en Solana