absolute_beginners
Overview
Odin for Absolute Beginners
This program shows the most basic features of Odin. It is meant for total beginners with very little programming experience.
To start learning, open 1_main.odin and read from the top.
You can run the example by navigating to this folder in a command-prompt or terminal and typing:
odin run .
However, the output might not make much sense without reading the code.
See https://odin-lang.org/docs/ for additional Odin learning material.
Code
1_main.odin ¶70 linesSource
1// This example shows a few basic Odin features. It's targeted at people with
2// very little programming experience.
3
4// When you ran `odin run .`, then all the `.odin` files in this folder were
5// compiled into a single package. That package was turned into an executable
6// and then started.
7
8// This is the package name. All files in a package must use the same package
9// name. The package name must be unique project-wide (no other imported package
10// may use the same name).
11package basics
12
13// This imports the `fmt` package from the core collection. You find the core
14// collection in `<odin>/core`, where `<odin>` is the folder where you installed
15// Odin. `fmt` is just a subfolder of `core`. Again, packages are just folders!
16import "core:fmt"
17
18// This is a procedure. A procedure contains code that can be executed. This
19// procedure is special: By default, the program starts in the procedure called
20// `main`.
21main :: proc() {
22 // The `fmt.println` procedure is part of the `core:fmt` package. It prints
23 // text to the "standard output stream", which could mean:
24 // - Terminal
25 // - Command prompt
26 // - Code editor output window
27 fmt.println("Hellope!") // Prints "Hellope!" to the console
28
29 // This runs another procedure called `variables`. But there is no such
30 // procedure in this file! Where is it? All files within this folder are
31 // part of the same package. So this procedure can be in any of the `.odin`
32 // files in this folder. In this case it is in `2_variables.odin`. Open that
33 // one to see what it does!
34 variables()
35
36 // When the `variables` procedure finishes running, then the program will
37 // continue with the next line. This runs a procedure called `loops`. Note
38 // that we feed the value `21` into it. You'll find `loops` in `3_loops.odin`.
39 loops_result := loops(21)
40
41 // `loops` returned a value. We've put that in a new variable called
42 // `loops_result`. We can send that value into the next procedure:
43 // `if_statements`. You'll find that procedure in (you guessed it!)
44 // `4_if_statements.odin`.
45 if_statements(loops_result)
46
47 // Let's move on and read about what structs are! Continue in `5_structs.odin`
48 cat := structs()
49
50 // That procedure returned a whole struct of type `Cat`! We can print the
51 // contents of it:
52 fmt.println(cat) // Cat{name = "Klucke", age = 5}
53
54 // We are nearing the end of this program. Let's finish with looking at what
55 // pointers are. Note how we write `&cat` when running the `pointers`
56 // procedure. That fetches the memory address of `cat` and sends it into the
57 // `pointers` procedure. More about that in `6_pointers.odin`!
58 pointers(&cat)
59
60 // `pointers` modified the age of `cat` from `5` to `11`. It did so by
61 // writing to the age field through the pointer we sent into `pointers`.
62 fmt.println(cat) // Cat{name = "Klucke", age = 11}
63
64 // One note before we end: This example is split into a bunch of files, with
65 // just a single procedure in each. Usually you'll have much bigger files
66 // in Odin, where each file has lots of procedures, structs and all that.
67
68 // That's it for this example! There is A LOT more to discover. Have a look
69 // at the resources available here: https://odin-lang.org/docs/
70}
2_variables.odin ¶98 linesSource
1// As you can see, this is the same package name as in `1_main.odin`. Which is
2// required. Also: The filename `2_variables.odin` and `1_main.odin` are
3// numbered like that just to make you look in `1_main.odin` first. The file
4// names don't matter at all: By default, any file within a package can use
5// anything from any other file within that package.
6package basics
7
8import "core:fmt"
9
10// This procedure contains some examples on how to use variables. Variables are
11// like the name hints possible to vary. You can change their value and read
12// their contents.
13variables :: proc() {
14 // This creates an integer variable. It's called `number`. It is of type
15 // `int`, short for integer. It can only store whole numbers.
16 //
17 // We didn't supply any value, so it has the value `0` by default.
18 number: int
19
20 fmt.println(number) // 0
21
22 // This gives `number` a new value. If there was no pre-existing variable
23 // with the name `number`, then this would not compile.
24 number = 7
25
26 fmt.println(number) // 7
27
28 // However, you can create variable and give it a value in one line! This
29 // looks like `number: int`, but we tucked on `= 10` at the end.
30 another_number: int = 10
31
32 fmt.println(another_number) // 10
33
34 // This line also creates a new variable. But it doesn't say which type it
35 // should have. Instead, the compiler uses "type inference": It figures out
36 // the type by looking at the value on the right side of `:=`. In this case
37 // the type is inferred to `int`.
38 yet_another_number := 42
39
40 fmt.println(yet_another_number) // 42
41
42 // Let's use another type: This variable has the type `f32`. That's short
43 // for "floating point 32 bit". Such a type can store a number with both
44 // a whole part and also a fractional part.
45 //
46 // Just like before, the default value is `0`.
47 float_number: f32
48
49 fmt.println(float_number) // 0
50
51 // It's possible to assign numbers that contain a fractional part to
52 // variables of type `f32`. Note that this would not work if the type was `int`.
53 float_number = 7.2
54
55 // This might actually print something like `7.1999998`. Floating point
56 // numbers have a limited precision, trying to print a lot of decimals can
57 // make that limited precision apparent.
58 fmt.println(float_number) // 7.1999998
59
60 // To limit the number of printed decimals, you can use `printfln` instead
61 // of `println`. That procedure accepts two arguments: The first one is
62 // a format string: It describes how to print the variable we feed into it.
63 // The format string we use is `%.1f`. It says that we want to print a
64 // floating point number with a single decimal.
65 fmt.printfln("%.1f", float_number) // 7.2
66
67 // Here we again create a new variable and try to infer the type. But the
68 // inferred type will not be `f32`. It will be `f64`. The default inferred
69 // type for numbers with a fractional part is `f64`.
70 another_float_number := 7.2
71
72 // This prints the type of a variable! We can use it to verify that the type
73 // of `another_float_number` is indeed f64
74 fmt.println(typeid_of(type_of(another_float_number))) // f64
75
76 // So if you want to declare an f32 and give it a value on a single line,
77 // then you must say what type it should have. You can do this in two ways.
78 //
79 // 1) This creates a variable of type `f32` and assign `123.4` to it.
80 i_want_a_f32: f32 = 123.4
81
82 fmt.printfln("%.1f", i_want_a_f32) // 123.4
83 fmt.println(typeid_of(type_of(i_want_a_f32))) // f32
84
85 // 2) This creates a variable and infers the type from the right-hand side.
86 // `f32(2025.1)` casts the value `2025.1` to the type `f32`. So the
87 // right-hand side has type `f32`.
88 i_want_another_f32 := f32(2025.1)
89
90 fmt.printfln("%.1f", i_want_another_f32) // 2025.1
91 fmt.println(typeid_of(type_of(i_want_another_f32))) // f32
92
93 // There's a list of all available so-called 'basic types' (int, f32 etc) in
94 // the overview: https://odin-lang.org/docs/overview/#basic-types
95
96 // That's it for this procedure! It ends here, which means that the program
97 // will continue with the next line after `variables()` in `1_main.odin`.
98}
3_loops.odin ¶60 linesSource
1package basics
2
3import "core:fmt"
4
5// This procedure has a parameter. Note the `(n: int)` just after `:: proc`.
6//
7// When `main` called this procedure it supplied the value `21` as a procedure
8// argument. That argument will be available within the procedure parameter `n`.
9//
10// Note how `n: int` looks like a variable declaration! `n` is used near the end
11// of the procdure.
12//
13// Also note something else: It says `-> int` on the next line. This means that
14// this procedure will return an integer number back to the whoever ran it.
15loops :: proc(n: int) -> int {
16 fmt.println(n) // prints "21" because it says `loops(21)` in `1_main.odin`.
17
18 // Let's make a loop that runs 5 times! You can do that in several ways.
19
20 // This loops from 0 to 4 and for each lap of the loop the number is
21 // available in the loop variable `i`.
22 for i in 0..<5 {
23 fmt.println(i) // 0, 1, 2, 3, 4
24 }
25
26 // Same thing, but different kind of loop:
27 for i := 0; i < 5; i += 1 {
28 fmt.println(i) // 0, 1, 2, 3, 4
29 }
30
31 // This loop lives inside some extra curly braces. That makes `i` not exist
32 // outside those curly braces. Handy, so I don't get collisions with other
33 // variables called `i` later in this procedure!
34 {
35 // This looks like the previous loop, but I've moved out the `i := 0`
36 // and I put the `i += 1` inside the loop. It has the same effect.
37 i := 0
38 for i < 5 {
39 fmt.println(i) // 0, 1, 2, 3, 4
40 i += 1
41 }
42 }
43
44 // Note how all the loops above use the word `for`. All loops in Odin use
45 // `for`. There is no `while` or `foreach` keyword like in some languages.
46
47 // We can use the procedure parameter `n` to loop that many times.
48 res := 0
49
50 for i in 0..<n {
51 fmt.println(i) // 0, 1, 2, ... , 19, 20
52 res += i
53 }
54
55 // What's this `res` thing? This procedure has a return value, so I thought
56 // I'd better return something... So I made `res` into a sum of all the
57 // numbers in the previous loop.
58
59 return res
60}
4_if_statements.odin ¶45 linesSource
1package basics
2
3import "core:fmt"
4
5if_statements :: proc(some_number: int) {
6 // An if statement runs some code only if a condition is true.
7 //
8 // The condition goes between the `if` and the `{`. If the condition is
9 // `true` then the code between the curly braces runs.
10 if true {
11 fmt.println("This is always happens.")
12 }
13
14 // You can use `>` to check if a value is bigger than another value.
15 if some_number > 10 {
16 fmt.printfln("some_number is %v, which is bigger than 10!", some_number)
17 }
18
19 // Unless you changed something in the example, then `some_number` will be
20 // `210`. So the following call to `println` will not run!
21 if some_number > 300 {
22 fmt.println("some_number is bigger than 300!")
23 }
24
25 // This `>` thing is called a comparison operator. Odin has a bunch of
26 // comparison operators. All of them result in a value of type `bool`, short
27 // for 'boolean'. A bool can only have the value true or false.
28 //
29 // There's a list of all comparison operators here:
30 // https://odin-lang.org/docs/overview/#comparison-operators
31 //
32 // You can assign the result of the comparison operator to a variable. Note
33 // how we don't write any type: It's inferred to being of type `bool`:
34 a_condition := some_number < 500
35
36 // This will print.
37 if a_condition {
38 fmt.println("some_number is less than 500")
39 }
40
41 // Use ! to invert a boolean. This will not print anything.
42 if !a_condition {
43 fmt.println("some_number is equal to 500, or larger")
44 }
45}
5_structs.odin ¶55 linesSource
1package basics
2
3import "core:fmt"
4
5// This defines a new type that we can use in our code. A struct is essentially
6// like a group of several variables. You can send a struct into a procedure and
7// treat it like a single thing. That way you don't have to juggle a million
8// variables. Handy!
9Cat :: struct {
10 // These are called the fields of the struct. The `name` field if of type
11 // string, it can store text. Note how the fields look like variables, but
12 // with a comma at the end.
13 name: string,
14 age: int,
15}
16
17// This procedure returns a whole struct!
18structs :: proc() -> Cat {
19 // This makes a new variable of type `Cat`. Since we don't provide a value,
20 // it is zero-initialized. This means that the `name` and the `age` fields
21 // are all zeroed.
22 cat1: Cat
23
24 // This prints the whole struct! Note how the age is zero and the name is
25 // "" (empty string)
26 fmt.println(cat1) // Cat{name = "", age = 0}
27
28 // Let's give cat1 a name and an age:
29 cat1.name = "Pontus"
30 cat1.age = 7
31
32 fmt.println(cat1) // Cat{name = "Pontus", age = 7}
33
34 // Just like with other types, you can create and initialize a type on a
35 // single line:
36 cat2 := Cat {
37 name = "Klucke",
38 age = 5,
39 }
40
41 fmt.println(cat2) // Cat{name = "Klucke", age = 5}
42
43 // You can re-initialize a struct by assigning to it (note: We only use `=`,
44 // not `:=`):
45
46 cat1 = {
47 name = "Tom",
48 age = 23,
49 }
50
51 fmt.println(cat1) // Cat{name = "Tom", age = 23}
52
53 // Let's return the whole `cat2` struct!
54 return cat2
55}
6_pointers.odin ¶23 linesSource
1package basics
2
3import "core:fmt"
4
5// This procedure has a parameter that is of type `^Cat`. Read that as:
6// "pointer to Cat". The `^Cat` type contains a memory address. We can go
7// through that pointer in order to modify the memory that lives there.
8pointers :: proc(cat: ^Cat) {
9 // Printing a pointer shows the value at that memory address.
10 fmt.println(cat) // &Cat{name = "Klucke", age = 5}
11
12 // But we can also use the format string "%p" to directly print the memory
13 // address it contains. This is not super-important, but interesting to see
14 // that the pointer is just a number!
15 fmt.printfln("%p", cat) // 0x52EF52F878
16
17 // This will go through the pointer `cat` and modify the `age` field. The
18 // procedure that called this procedure (main) will be able to see these
19 // changes as well.
20 cat.age = 11
21
22 fmt.println(cat) // &Cat{name = "Klucke", age = 11}
23}