So I haven’t actually done much with Go. I didn’t know until after I finished my Rust project that Go is an easier language to learn. Maybe that’s a good thing so that I tackled the more difficult language first.
From reading comparisons on the web:
Go is easier to learn and is focused on productivity, which means it’s faster to write something useful.
Go is easier to learn than Rust But with Go, there can be data races. There is also a garbage collector during runtime.
With Rust, you can control safety and performance much better.
That said, I think it’s definitely worthwhile to investigate Go to see if it is more optimal for your application.
And if you search for Go vs Rust, there are a ton of opinions on the web
Rust happened to have a few community initiatives to get people to try Rust.
Taking part in Increasing Rust’s Reach is a big reason why I persisted with my little project to learn Rust.
Visual Studio search is now powered with ripgrep
The Rust team is also very conscientious about recognizing new contributors.
Because when you’re new to a project, it’s always nice to be acknowledged for your contributions and you feel welcomed into the community.
If your Rust code compiles, there are all these safety guarantees of the compiled code.
The code is statically checked at compile time, and there is no runtime cost and no garbage collector.
So Rust is pretty awesome.
What is the downside?
There is a steep learning curve, it’s complex in syntax and semantics, and there are many people that think it’s not mature enough for production code.
So I had a Raspberry Pi and a Sense HAT lying around so I decided I’d try to write some Rust code that would run on this platform.
The Raspberry Pi is an embedded computer running Linux.
An embedded device is resource constrained in memory and computing power compared to your laptop, desktop, or server.
The Sense HAT is an adapter board with sensors and an LED matrix. I decided that I would try to write a Rust script that will light up the LED matrix.
The is the official Python module for the Sense HAT.
Notice that there are a bunch of sensors on the Sense HAT that you can control and read by running Python code on the Raspberry Pi.
Basically, I examined how the functionality was implemented in Python to figure out how to implement it in Rust.
This is an example from the Python Sense HAT library to write out to the LED matrix.
You define an 8x8 pixel map with each pixel specified with R, G, B values of an int between 0 to 255.
I then looked into how the set_pixels() function is implemented.
So if we look at set_pixels(),
There is some error handling code
And then we open an fb device
Fb stands for framebuffer, a device driver for graphics display.
This function will store a bitmap in memory that drives the graphics display.
Let’s take a look at how we can find the location of fb_device
To find the location of the framebuffer device, it calls this function _get_fb_device()
This function iterates through all the framebuffer devices listed in /sys/class/graphics/
If there is a `name` file,
It checks the contents to see if it matches the variable defined in SENSE_HAT_FB_NAME
Let’s see what framebuffer devices can be found on the raspberry pi
There are 3 devices but only 2 have the `name` file. Fb0 and fb1
Let’s take a look at the contents in the name files for fb0 and fb1
Fb0 contains BCM2708 FB, and if you run a web search online, it tells us that fb0 refers to the framebuffer for the raspberry pi CPU
Fb1 contains the framebuffer device driver for the sense hat.
Going back to _get_fb_device(), we parse fb1 and concatenate with /dev to get the device path /dev/fb1
This is the location of the sense hat framebuffer device driver
So let’s just summarize the function to find the framebuffer device driver:
Look for all available framebuffers
Check which one is associated with the sense hat
Return location of that framebuffer device driver
So this is what I came up with in Rust.
This is just to give you a sense of what it looks like in Rust.
This is the first part of the function to find the framebuffer device driver location.
A couple things to note:
? Is shorthand for a type of error handling
With error handling, I noticed that even in the past couple years, the core team is still working on better ergonomics, which is just how to make it more human programmer friendly.
So one thing that I think is interesting about following the Rust project now is to follow how a new language evolves. Many of the language decisions are discussed openly on their user forum.
Another basic feature in Rust is the enumeration, or enum.
and a basic enum that is used frequently is the Option,
Which is used to represent something or nothing.
The None variant of an Option indicates failure or lack of value.
Otherwise, the Some variant indicates the value is present.
Many of you may be familiar with the null value used in other languages. In Rust, the replacement is the None variant of an Option.
So now let’s take a look at the second half of the Rust function to find the framebuffer location
Note that fb_device was instantiated as None variant of an Option.
In this line, we parse fb1 and concatenate it with /dev and then wrap that value with Some() and assign it to fb_device
Then we check if the fb device driver path exists on the system, and assign Some or None to fb_device
Well, there is also a Result enum with the variants Ok or error.
So both the Option enum and Result enum are used very frequently in Rust code.
This is for recoverable error handling when you don’t want the program to stop immediately when the error occurs.
A concept that I didn’t have think too much about in Python is memory management.
However, understanding the stack vs the heap in Rust is important.
Fixed size data is allocated on the stack.
The stack is deallocated at the end of the function call.
You can assume stack-only data is copied.
Examples of types that are allocated on the stack are Path, string slice, and array.
Data that can grow in size goes on the heap
However, ownership moves, unless the clone trait is implemented
Data on the heap is also used to pass memory between functions.
Examples of types with data allocated on the heap are PathBuf, String, and vector.
Ownership and borrowing are a central feature in Rust.
It’s how Rust can be memory safe without a garbage collector.
Here is an example from the Rust book.
The equivalent version in Python wouldn't require much thought, it would be very straightforward and would run fine.
However, in Rust, a String is a heap allocated data type, which means ownership moves.
And trying to compile this Rust code would result in a compiler error, because the data located at s1 moves to s2.
So this is a representation of String s1.
The block on the left is stored on the stack.
There is a pointer to memory that holds the contents of the string, a length, and a capacity.
The block on the right is memory on the heap that holds the contents.
So when we assign s1 to s2, the stack data is copied.
Which means we copy the pointer, length and capacity.
However, now we have two pointers to the same data in memory.
When a variable goes out of scope, Rust automatically cleans up the heap memory for that variable.
This would be a problem and cause a double free error, which is a memory safety bug and could lead to security vulnerabilities
So in Rust, s1 is no longer valid once the heap data is assigned to another variable.
This is called a move.
So if you want to copy the data on the heap, you can implement a clone trait and this makes a deep copy.
So here is an example of specifying the pixel_list with arrays.
Arrays are stack-only data.
It’s fixed size so it can’t grow.
Since x and o are stored entirely on the stack,
when forming pixel_list, a stack copy will be made for each o and x assignment.
In this example, we use vectors which is a type where the data is allocated on the heap.
So in this case, when forming pixel_list,
On the first o assignment, ownership moves.
Then on the second o assignment, o has already been deallocated.
So this example won’t compile.
If I really wanted to use vectors to form
So if I really wanted to specify pixel_list with vectors, I could clone on every x and o.
If you really needed the data on the heap, so that it can grow or pass to another function.
In my case, I just used the array version.
Generally stick to stack-only data if possible.
It makes your life easier.
However, you may want to use a heap allocated type if you need to pass data to another function.
So in the Python version of the function header, no types need to be specified.
However, in the Rust version, you have to define the correct types.
Here is a quick overview of the different types in the Rust version:
Path is fixed size, allocated on the stack. Ampersand Path is a reference to a Path. PathBuf is the owned mutable version of path. It can grow and be allocated on the heap.
I first tried to return a Path, since I figured that a Path is fixed size, but that resulted in a compiler error, until I changed the type to PathBuf.
And it’s because you have to use a heap allocated type for passing data to another function.
So this is really difficult and confusing, and I still don’t understand it very well.
I recommend reading about it in the Rust book.
I have to read the Rust book over and over and over again whenever I’m writing Rust code.
So while I was learning Rust, it really felt like an accomplishment even when I got a short snippet to compile.
Now I’m going to give you a sense of the cryptic compiler errors you will encounter while learning Rust.
In this case, notice that there are two types of Errors, so I first tried to define the return type as standard Error.
From the compiler error message, it’s unclear whether I had to write a trait or define a size or do something with static.
And as I mentioned before, there is not as much help online for Rust errors compared to Python errors.
Then at some point, I figure out that I have to use io::Error.
Figuring out how to fix the code from these compiler errors comes with experience.
When you search for help online, less posts for Rust compared to Python.
Also, Rust as a language is rapidly evolving, so posts from 2015 may not be relevant anymore.
Sometimes I find myself adding some `&s` and `mut` to see if code compiles.
This is why it's an experiment. Sometimes you just have to try different things to learn and then the concepts may start to make sense.
Another concept I had to look into was bit manipulation,
as the pixel list is encoded as binary data then written out to the framebuffer.
The sensehat framebuffer requires the 16bit RGB565 format, commonly used in embedded devices.
So in the function, we shift some bits and apply a mask.
That makes a 16 bit int type.
And then the Python struct library is used to interpret that 16 bit int as packed binary data.
It looks pretty similar in the Rust code,
Except I had to figure out what is the equivalent Rust crate to use.
Then I figured out that byteorder is used to encode and decode in Little Endian or Big Endian order.
Just a note, Intel processors are Little Endian and ARM processors are Big Endian.
So I was writing the code on my Macbook Pro, but when I compiled the code on the Raspberry Pi, the Endian format changed.
However, then I found that I can just call NativeEndian, so all is good.
So I didn’t want to go over every tedious detail of figuring out the Rust script, but I wanted to give you a sense of the process.
I’m happy to report that I got it working and the full script is on my github page.
This is a photo of the question mark pattern displayed on the LED matrix.
Encourage you to have your own experiments, whether it be in Rust or some technical topic you don't know much about
exercism: short exercises to get you going, writing some Rust code
I encourage you to connect with others in the Rust community.
Look for your local meetup
There is also an official user forum at users.rust-lang.org
There are 3 conferences: RustConf on the west coast, Rust Belt Rust in the Rust Belt, and Rust Fest in Europe.
The community team has also been running a full-day RustBridge workshop for beginners.
I have also heard that a few Rust core team member frequent #rust-beginners to answer questions.
• Created in 2010
• Maximized safety
• Complex syntax and
• Created in 2009
• Easier to learn
• Data races
• Garbage collector
Designed for multi-core systems
arrangement of words
Who uses Rust?
- Mozilla (Servo / Quantum)
- Other projects using Rust in production:
Steep learning curve
Raspberry Pi ($35 USD) Sense HAT ($40 USD)
HAT = Hardware Attached onTop- Embedded computer running Linux
See what’s in /sys/class/graphics/fb* on the RPi
device = /dev/fb1
to CPU on RPi
Find Sense HAT fb device driver
1. Look for all FBs listed in `/sys/class/graphics/fb*`
2. Read the `name` file in each, if it exists
3. Check if the file contents match the given `SENSE_HAT_FB_NAME`
4. Return device path (i.e., /dev/fb1)
`?` is shorthand for a type of error handling
“name” file exists
2.Error handling Still working on better ergonomics
Human programmer friendly
This is for recoverable error handling,
when you don’t want the program
to stop immediately when the error occurs.
- Fixed size data
- Removed at the end of a function
- Data can be copied (within the function)
- Types: Path, &str (string slice), array
- data that can grow in size
- Ownership moves, unless a deep copy (clone) trait is implemented
- Used to pass memory between functions
- Types: PathBuf, String,Vec
Does NOT compile!
Central feature of Rust
Memory safety without
Path: slice of path (fixed size, stack-only)
&Path: reference to a `Path`
PathBuf: owned, mutable path (data allocated on heap)
PathBuf to pass to another function
Passing data on the heap
Difficult and confusing concept!
Read more at:
Example of cryptic compiler errors
Shift bits and apply mask
Intel (Macbook Pro) = Little Endian, ARM (RPi) = Big Endian
24-bit pixel (8-bit R, 8-bit G, 8-bit B)
to 16 bit RGB565 format
See full writeLED-rs script at github.com/anna-liao