Learn how native programs distinguish instructions for different functions.
borsh
crate and the derive
attribute to provide Borsh
deserialization and serialization functionality to Rust structsmatch
expressions help create conditional code paths based on the
provided instructionlet
keyword.
mut
keyword. Defining a variable with
this keyword means that the value stored in it can change.
LightStatus
enum has two possible variants in this situation: it’s either
On
or Off
.
You can also embed values into enum variants, similar to adding fields to a
struct.
On
variant of LightStatus
requires also setting the value of color
.
switch
statements in C/C++. The match
statement allows you to compare a value against a series of patterns and then
execute code based on which pattern matches the value. Patterns can be made of
literal values, variable names, wildcards, and more. The match statement must
include all possible scenarios, otherwise the code will not compile.
impl
keyword is used in Rust to define a type’s implementations. Functions
and constants can both be defined in an implementation.
boo
here can only be called on the type itself rather than an
instance of the type, like so:
answer
requires a mutable instance of Example
and can be called
with dot syntax:
derive
attribute macro and some traits provided by the borsh
crate, so it’s
important you have a high level understanding of each.
Traits describe an abstract interface that types can implement. If a trait
defines a function bark()
and a type then adopts that trait, the type must
then implement the bark()
function.
Attributes add
metadata to a type and can be used for many different purposes.
When you add the
derive
attribute
to a type and provide one or more supported traits, code is generated under the
hood to automatically implement the traits for that type. We’ll provide a
concrete example of this shortly.
NoteInstruction
enum comes with embedded data
that will be used by the program to accomplish the tasks of creating, updating,
and deleting a note, respectively.
borsh
crate. This crate
provides traits for BorshDeserialize
and BorshSerialize
that you can apply
to your types using the derive
attribute.
To make deserializing instruction data simple, you can create a struct
representing the data and use the derive
attribute to apply the
BorshDeserialize
trait to the struct. This implements the methods defined in
BorshDeserialize
, including the try_from_slice
method that we’ll be using to
deserialize the instruction data.
Remember, the struct itself needs to match the structure of the data in the byte
array.
unpack
that
accepts the instruction data as an argument and returns the appropriate instance
of the enum with the deserialized data.
It’s standard practice to structure your program to expect the first byte (or
other fixed number of bytes) to be an identifier for which instruction the
program should run. This could be an integer or a string identifier. For this
example, we’ll use the first byte and map integers 0, 1, and 2 to instructions
create, update, and delete, respectively.
split_first
function on the input
parameter to return a tuple. The first element, variant
, is the first byte
from the byte array and the second element, rest
, is the rest of the byte
array.try_from_slice
method on
NoteInstructionPayload
to deserialize the rest of the byte array into an
instance of NoteInstructionPayload
called payload
match
statement on variant
to create and
return the appropriate enum instance using information from payload
ok_or
and unwrap
functions are used for error handling and will be
discussed in detail in another lesson.
match
statement.
lib.rs
. You must register every file in your program like this.
use
statements in other files will need to be prefaced with the pub
keyword:
lib.rs
file.
Inside lib.rs, we’re going to bring in the following crates and define where
we’d like our entry point to the program to be with the entrypoint
macro.
instruction.rs
. Inside this
new file, add use
statements for BorshDeserialize
and ProgramError
, then
create a MovieInstruction
enum with an AddMovieReview
variant. This variant
should have embedded values for title,
rating
, and description
.
MovieReviewPayload
struct. This will act as an intermediary
type for deserializtion so it should use the derive
attribute macro to provide
a default implementation for the BorshDeserialize
trait.
MovieInstruction
enum that defines
and implements a function called unpack
that takes a byte array as an argument
and returns a Result
type. This function should:
split_first
function to split the first byte of the array from the
rest of the arrayMovieReviewPayload
match
statement to return the AddMovieReview
variant of
MovieInstruction
if the first byte of the array was a 0 or return a program
error otherwiselib.rs
file
to handle some of our program logic.
Remember, since we added code to a different file, we need to register it in the
lib.rs
file using pub mod instruction;
. Then we can add a use
statement to
bring the MovieInstruction
type into scope.
add_movie_review
that takes as arguments
program_id
, accounts
, title
, rating
, and description
. It should also
return an instance of ProgramResult
Inside this function, let’s simply log our
values for now and we’ll revisit the rest of the implementation of the function
in the next lesson.
add_movie_review
from process_instruction
(the
function we set as our entry point). To pass all the required arguments to the
function, we’ll first need to call the unpack
we created on
MovieInstruction
, then use a match
statement to ensure that the instruction
we’ve received is the AddMovieReview
variant.
instruction_data
and creates an account to store the data onchain.
Using what you’ve learned in this lesson, build the Student Intro program to the
point where you can print the name
and message
provided by the user to the
program logs when the program is invoked.
You can test your program by building the
frontend
we created in the
Serialize Custom Instruction Data lesson and then
checking the program logs on Nexis Native Chain Explorer. Remember to replace the program ID
in the frontend code with the one you’ve deployed.
Try to do this independently if you can! But if you get stuck, feel free to
reference the solution code.