slices/prefer_to_pass_slices
Code
prefer_to_pass_slices.odin ¶128 linesSource
1/*
2This example shows why one might prefer procedure parameters that are slices
3whenever possible. Within `main` a dynamic array is created. The program uses
4three different procedures to interact with the array. Two out of three
5procedures take a slice parameter rather than a dynamic array parameter. There
6are comments that motivate the choice of parameter type.
7
8The code is from "Understanding the Odin Programming Language"
9(https://odinbook.com/). It is used with permssion from the author.
10*/
11
12package prefer_to_pass_slices
13
14import "core:fmt"
15import "core:math/rand"
16
17Cat :: struct {
18 name: string,
19 age: int,
20}
21
22/*
23Note how `add_cat_of_random_age` is fed a pointer to the dynamic array.
24`print_cats` and `mutate_cats` are fed a slice that looks at the whole dynamic
25array. See those procedures to understand why.
26*/
27main :: proc() {
28 all_the_cats: [dynamic]Cat
29 add_cat_of_random_age(&all_the_cats, "Klucke")
30 add_cat_of_random_age(&all_the_cats, "Pontus")
31
32 print_cats(all_the_cats[:])
33 mutate_cats(all_the_cats[:])
34 print_cats(all_the_cats[:])
35
36 /*
37 Output of program (the numbers will be different on each run):
38
39 Klucke is 8 years old
40 Pontus is 13 years old
41 Klucke is 10 years old
42 Pontus is 2 years old
43 */
44}
45
46/*
47This procedure makes changes to a dynamic array (appends items). So we must pass
48a pointer to the dynamic array. After all, the only way to use `append` is if
49you have something of type `^[dynamic]Element_Type`.
50*/
51add_cat_of_random_age :: proc(cats: ^[dynamic]Cat, name: string) {
52 random_age := rand.int_max(12) + 2
53 append(cats, Cat {
54 name = name,
55 age = random_age,
56 })
57}
58
59/*
60This procedure loops over the parameter `cats: []Cat` and prints some info about
61each element. Note how the slice operator `[:]` is used in `main`:
62
63 print_cats(all_the_cats[:])
64
65It feeds this procedure a slice that looks at the whole dynamic array.
66
67Creating slices is very cheap. Here's what a slice looks like internally:
68
69 // From `<odin>/base/runtime/core.odin`
70 Raw_Slice :: struct {
71 data: rawptr,
72 len: int,
73 }
74
75So when you do `print_cats(all_the_cats[:])`, then a slice is created with the
76`data` field pointing to the first element of `all_the_cats` and `len` is set to
77the length of the dynamic array.
78
79This means that slicing is very cheap: There are no extra allocations.
80
81Because this procedure uses a slice, we can also use it with any array type that
82supports slicing. A fixed array would work fine:
83
84 fixed_array_of_cats := [3]Cat { bla bla }
85 print_cats(fixed_array_of_cats[:])
86
87This makes the procedure more generally useful compared to if it accepted an
88array of type `[dynamic]Cat`.
89*/
90print_cats :: proc(cats: []Cat) {
91 for cat in cats {
92 fmt.printfln("%v is %v years old", cat.name, cat.age)
93 }
94}
95
96/*
97This procedure loops over the parameter `cats: []Cat` and modifies each element.
98
99This may surprise some people: The parameter type isn't `^[]Cat`. So how can it
100modify the elements?
101
102All procedure parameters in Odin are immutable, but it's just the fields of the
103`Raw_Slice` that are immutable:
104
105 Raw_Slice :: struct {
106 data: rawptr,
107 len: int,
108 }
109
110We can't change what address `data` contains or the value of `len`. But the loop
111in this procedure doesn't do any of that. It just goes to the memory that the
112pointer `data` refers to and modifies the memory that lives at there, which is
113allowed!
114
115Passing a slice here makes this procedure more generally useful compared to if
116it used a parameter of type `[dynamic]Cat`.
117
118We could for example create a fixed array of 100 cats and give them all a
119random age using this procedure:
120
121 lots_of_cats: [100]Cat
122 mutate_cats(lots_of_cats[:])
123*/
124mutate_cats :: proc(cats: []Cat) {
125 for &cat in cats {
126 cat.age = rand.int_max(12) + 2
127 }
128}