hecto, Chapter 1: Setup
đď¸ Construction Notice
The old (complete, but outdated) version of hecto
is available here.
You are looking at the 2024 version of hecto
, which is still unfinished.
You can follow the progress of the rewrite here.
Once itâs done, this notice will be deleted.
Table of Contents
- Introduction
- Chapter 1: Setup đ You are here
- Chapter 2: Entering Raw Mode
- Chapter 3: Raw Input and Output
- Chapter 4: A Text Viewer
- Chapter 5: A Text Editor
- Chapter 6: Search
- Chapter 7: Syntax Highlighting
- Appendices
- Change Log
Chapter 1: Setup
Ahh, step 1. Don't you love a fresh start on a blank slate? And then selecting that singular brick onto which you will build your entire palatial estate?
Unfortunately, when you're building a computer program, step 1 can get... complicated. And frustrating. You have to make sure your environment is set up for the programming language you're using, and you have to figure out how to compile and run your program in that environment.
Fortunately, the development environment that Rust comes with does most of the things for us, so you donât need anything besides a text editor1 and rust
. This tutorial was written on a Mac, though it should work on Windows and Linux as well.
How to install Rust
In order to install Rust, weâre going to use rustup
, which manages installed Rust versions and associated tools.
Rust comes with a fantastic, free book: The Rust Programming Language. I will link to this book often for more in-depth knowledge, whenever I think the book explains it better than I do, To install rustup
, just follow Chapter 1.1: Installation for your platform. At the time of writing, it works like this on Mac: Open a terminal (find it by typing âTerminalâ into Spotlight) and enter the following command:
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
The installer greets you with some options, and offers to just go with the default by hitting Enter
. The installation ends with the following encouraging words:
Rust is installed now. Great!
Restart your terminal, and then type the following:
rustc --version
You should see some information about the version youâve installed. If not, head over to the Installation guide for troubleshooting.
By the way, weâll use the terminal a lot in this tutorial, so just keep it open.
The main()
function
Create a new file named hecto.rs
and give it a main()
function.(hecto
is the name of the text editor weâre building).
fn main() {
return;
}
In Rust, you have to put all your executable code inside functions. The main()
function in Rust is special. It is the default starting point when you run your program. When you return from the main()
function, the program exits and passes control back to the operating system2 .
Rust is a compiled language. That means we need to run our program through a Rust compiler to turn it into an executable file. We then run that executable like we would run any other program on the command line.
To compile hecto.rs
, run rustc hecto.rs
in your terminal. If no errors occur, this will produce an executable named hecto
. To run hecto
, type ./hecto
and press Enter
. The program doesnât print any output3.
Compiling with cargo
rustc
is all we theoretically need to build hecto
, but Rust comes with some convenient things that will make development easier, and as you will see later, safer.
Rust comes with a program called cargo
. If you are used to the JavaScript
ecosystem, then cargo
can best be described as rustâs equivalent to npm
. It helps you manage your dependencies, compile the code and other things. With Rust being installed correctly, you can type the following command to invoke cargo:
cargo --version
Weâll use this program to get started with hecto. Delete your previous hecto.rs
and the executable hecto
again by typing rm hecto.rs hecto
.
In a directory where youâd like hecto
to come to life, type the following:
cargo init hecto --vcs none
Using --vcs none
part ensures that you init hecto
without git
support. If you want to use git
in this project, omit this flag (but then youâd need to have git
installed)
cargo
claims success by telling you the following:
Created binary (application) package
It created a new folder called hecto
, populated with some files. We will go through most of them in a bit, the most interesting one is a file called main.rs
in a folder called src
.
Letâs look at that one. When you open it, youâll find that it already contains a main()
. Just by looking at it, we can infer that this program follows the ritual of the ancients by printing out "Hello, World!" and exiting.
To use cargo
to compile this program, make sure you are in the hecto
folder (Run cd hecto
after cargo init hecto
), and then run cargo build
in your shell. cargo
is not silent as rustc
was and will produce output that looks a bit like this:
Compiling hecto v0.1.0 (/Users/pflenker/repositories/hecto)
Finished dev [unoptimized + debuginfo] target(s) in 12.36s
Running this command adds a few more files (which weâll look at in a second) and puts an executable called hecto
into the folder target/debug
. Letâs execute ./target/debug/hecto
to convince ourselves that this program does, indeed, print out Hello, World!
and then exit.
Understanding cargo
âs Extra Files (and Features)
Our hecto
directory now contains a whole bunch of things (some of them hidden, the specifics might look different on your machine):
- A folder called
src
, containing themain.rs
. - Two files named
Cargo.toml
andCargo.lock
- A folder called
target
with some hidden files in it and a folder calleddebug
. That folder contains more files and folders and our executable,hecto
.
What are all these extra things good for? Letâs learn.
The src
Folder
The src
folder is where all our source files will live. Right now, we only have one. Soon, there will be more.
Cargo.toml
and Cargo.lock
This file follows a config format called TOML and is used to tell the compiler a few things about the code itâs supposed to compile. We take a closer look at the default one a few paragraphs down..
cargo
also does dependency management for us, and the Cargo.toml
will hold these dependencies for us. The Cargo.lock
is part of that dependency management and ensures that dependencies stay consistent in different environments. No need to worry about that now - we will deep dive into that in the next chapter.
Build Targets
Letâs take a look at target
and its contents.
cargo
supports multiple so-called build targets. The one that is used as the default is debug
, meaning that the final executable is mainly targeted at us, the developers, and not at actual, real end customers.
Another valid target would be release
, which is the opposite: Something to put into the hands of the customers, not only for developers.
To build a release version, run:
cargo build --release
Once you do, you will notice that another folder appeared in the target
folder, aptly named release
.
Wait a minute, what does it mean, âthe executable is targeted at someoneâ, doesnât it only print âHello Worldâ, regardless of who uses it?
Well, kind of.
First of all, the Rust compiler tries to be very accommodating to developers and end users, therefore it treats debug
and release
builds differently. We will encounter a concrete example later, but there are several types of programming errors where Rust assumes that you really, really did not do this on purpose. If a debug
build encounters this kind of error, the program crashes, which is the most extreme thing Rust can to tell you just how wrong youâve been. But Rust does have a way to recover from these errors and can continue. With wrong results though, but it can continue, and assuming that for an end user, a wrong result is bad, but a crash is worse, the same error would not crash the release
build.
Secondly, the compiler can perform various optimisations under the hood to make the result faster. But these optimisations take time and make compilation slower. Who compiles all the time? Developers. Who compiles never? Users. Therefore, debug
builds disable the optimisations to prioritise speed in development, and release
builds enable them to prioritise speed in execution.
Lastly, the executable does contain code other than just âHello, Worldâ, and Rust adds information to the debug
build which will help you debug any issues quicker. This information isnât needed for customers, therefore it can be excluded from the release build.
cargo
also tells us as much during compilation, the final line for a debug
release reads like this:
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
This tells us that the build is not optimised and contains debug info.
The release
build, on the other hand, ends its compilation with:
Finished release [optimized] target(s) in 0.04s
This tells us that no debug info is contained, and that itâs an optimised build.
Letâs take a closer look at the debug info, but letâs not mess with our new and already beloved text editor code. Instead, head over to the Rust Playground, which helpfully already contains our hello world function. On the top left, you see some buttons Debug, then Stable, and then ...
. Click these 3 dots and enable backtrace
.
Then change the code in the playground as follows:
fn main() {
panic!("Hello, World");
}
panic!
triggers a crash, plain and simple. Click on âRunâ to run it. Then click on âDebugâ, change it to âReleaseâ, and run it again.
You will notice that therelease
build generates the following output:
Running `target/release/playground`
thread 'main' panicked at src/main.rs:2:4:
Hello, World
stack backtrace:
0: rust_begin_unwind
1: core::panicking::panic_fmt
2: playground::main
The debug
output looks like this:
Running `target/debug/playground`
thread 'main' panicked at src/main.rs:2:4:
Hello, World
stack backtrace:
0: rust_begin_unwind
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/std/src/panicking.rs:647:5
1: core::panicking::panic_fmt
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/panicking.rs:72:14
2: playground::main
at ./src/main.rs:2:4
3: core::ops::function::FnOnce::call_once
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/ops/function.rs:250:5
Without understanding stack traces yet, you can immediately see that the debug output contains more information than the release versions. Understanding the call stack, so the order in which functions have called other functions and then some more until one of them crashed, can be tremendously useful.
Build Artefacts
So that explains the folders, but what about the stuff in there that is not hecto
? Letâs investigate something to further our understanding.
Back in the terminal, run the following commands in order:
cargo clean
cargo build
cargo build
(Yes, we run cargo build
two times)
The first command cleans the target
directory. The second command runs as before. During third one, though, cargo
does not output a line starting with Compiling
, as it did before. That is because cargo
can tell that the current version of main.rs
has already been compiled. If the main.rs
was not modified since the last compilation, then cargo
doesnât bother running the compilation again. If main.rs
was changed, then cargo
re-compiles main.rs
. Once your codebase grows, this will become more useful, as most of the components shouldnât need to be recompiled over and over when youâre only making changes to one componentâs source code.
All (okay: most) of the extra files in the target
directory are build artefacts which enable cargo
to do subsequent compilations more quickly. Find more on this in ďżź cargoďżźâs
documentation.
Compiling and Running
Since it's very common that you want to compile and run your program, cargo
combines both steps with the command cargo run
.
Try changing the return value in main.rs
to a string other than Hello, World
. Then run cargo run
, and you should see it compile. Check the result to see if you get the string you changed it to. Then change it back to Hello, World
, recompile, and make sure it's back to returning Hello, World
.
Code Review
Letâs take a look together at the code.
If you follow the link above, youâll see an annotated commit. It shows you all the changes that have happened in the code between the previous step and the current one, with some comments directly on the line with the relevant code.
In this case, I am describing the contents of the Cargo.toml
in that commit.
Working with commits like this comes with a lot of upsides:
- You can see explanations directly on the code Iâm referring to, making it much easier to understand the code change.
- You can directly ask questions (GitHub account required) or comment on code fragments you donât understand
In the beginning it might be tiring for you to switch back and forth between this tutorial and GitHub. However, as soon as weâve learned the basics I am no longer going to show you what I did upfront so that you can repeat it - instead Iâm going to tell you the assignment first and later show you what I did to solve it, so youâll switch to GitHub less often.
I will link to a lot of additional material throughout this tutorial. Hereâs a convention I will stick to: Information crucial to understanding the tutorial will be either on the GitHub commits or directly here in the text. Any other link that is not clearly identifiable as a step on GitHub contains optional information that you can skip if youâre not interested.
Wrap-up and outlook
In this chapter, weâve installed Rust, initialised a bare-bone project and familiarised ourselves with it. We now know how to compile code by hand and with cargo
and have a good initial understanding of cargo
and the files it produces. Most importantly, we can build stuff and clean up behind us if need be. We also met the Rust Playground, which will sure come in handy once we want to investigate new things in isolation before we put them into action.
In Chapter 2, weâre going to build a program which reads user input, prints it on the screen and exits on pressing q
. That alone will hold a ton of beginnerâs learnings for us.
This post was last updated 1Â week ago.
At one point, youâll be able to edit
hecto
withhecto
, so you wonât even need a text editor any more.âŠRust supports asynchronous code, which we wonât cover in the tutorial. In this case, itâs perfectly possible for
main
to end but not pass control back to the Operating System yet.âŠNot sure about you, but creating an actual, real world, grown-up executable was, and somehow still is, a magical moment for me.âŠ