This document describes the draft for the Odin Track syllabus. It presents a reasonable list of concepts to be presented in the concept tree. As recommended in the Exercism Syllabus Doc, the tree will be implemented incrementally and, in the process, the exact list of concepts may change.
I looked at other tracks and, most of them, have between 35 and 45 concepts in their tree (with some going much higher - C-sharp has 62). Too many concepts may be intimidating to the student, too few may force us to bundle concepts that would better presented separately. The list below is mostly derived from the Go, Javascript, C++, C-sharp and Swift syllabus. Odin has a number of unique features which will be best addressed in their own unique concepts, for example "Memory Management and Allocators", "Structure of Arrays", "Matrix and SIMD Operations", etc. This increases the number of topics so, to try to keep the number in the 35-45 range, I bundled concepts which have been kept separate in other tracks where I thought they could be presented together. For example, I grouped integers and floating-point in a single topic (Number).
Some concepts are more important when getting started (core concepts) and other can be learned later. They may not even be needed in most of the practice exercises (advanced concepts).
I listed the concepts in the order they should be introduced, with pre-requisite concepts being located higher in the list.
Advanced concepts are marked with a (+).
They should be developed after the core concepts.
There are a couple of places where concepts have circular dependencies (like "Context" and "Memory Management & Allocator").
I picked an order for now but this may change once we start building the concept tree.
Also, because Odin is close to Go for most concepts (except interfaces/methods, garbage collection and goroutines), I was planning on borrowing heavily from the Go track concepts.
The goal is to have one exercise per concept. There may be exception if there is too much material to introduce in a single exercise, but in this case, we should consider splitting the concept into two smaller, more focused concepts.
The diagram below provides an overview of the concepts and the order in which they should be addressed. Only the most important dependencies are shown here to try to keep the diagram legible.
%%{
init: {"flowchart": {"rankSpacing": 400}}
}%%
graph TD
basics[Basics] --> packages[Packages]
basics --> numbers[Numbers]
basics --> booleans[Booleans]
basics --> strings[Strings & Runes]
numbers --> booleans
numbers --> variables[Variables & Assignment]
strings --> variables
variables --> constants[Constants]
variables --> conditionals[Control Flow: Conditionals]
booleans --> conditionals
variables --> pointers[Pointers]
packages --> procedures[Procedures]
pointers --> procedures
variables --> loops[Control Flow: Iteration]
procedures --> recursion[Recursion]
procedures --> parameters[Default Parameters, Named Arguments and Return Values]
loops --> arrays[Arrays]
variables --> structs[Structs]
variables --> enums[Enumerations]
structs --> unions[Unions]
parameters --> errors[Error Handling]
conditionals --> errors
unions --> errors
enums --> enum-arrays[Enumerated Arrays]
arrays --> enum-arrays
arrays --> bit-sets[Bit Sets]
enums --> bit-sets
pointers --> context[Implicit Context]
pointers --> memory-management[Memory Management & Allocators]
context --> memory-management
memory-management --> defer[Defer Statement]
procedures --> defer
arrays --> slices[Slices]
memory-management --> slices
slices --> dynamic-arrays[Dynamic Arrays]
memory-management --> dynamic-arrays
dynamic-arrays --> maps[Maps]
memory-management --> maps
numbers --> casts[Type Conversions]
procedures --> casts
procedures --> first-class-procedures[First Class Procedures]
parameters --> first-class-procedures
procedures --> generics[Parametric Polymorphism aka. Generics]
structs --> composition[Composition]
unions --> composition
errors --> or-statements[`or_…` statements]
parameters --> variadic-parameters[Variadic Parameters]
arrays --> variadic-parameters
parameters --> explicit-overloading[Explicit Procedure Overloading]
arrays --> matrix[Matrix and SIMD Operations]
structs --> structure-of-arrays[Structure of Arrays]
arrays --> structure-of-arrays
conditionals --> conditional-compilation[Conditional Compilation]
conditional-compilation --> attributes-directives[Attributes & Directives]
attributes-directives --> ffi[Foreign Function Interface]
ffi --> bit-fields[Bit Fields]
memory-management --> memory-tracking[Tracking Allocator]
conditionals --> testing[Test Framework]
procedures --> testing
strings --> regex[Regular Expressions]
loops --> concurrency[Concurrency]
packages --> concurrency
errors --> concurrency
There are currently 30 core concepts and 13 advanced concepts.
Each section includes the concept name, slug, the concept uuid, and the list of Odin syntax elements that should be introduced.
Present a getting-started program with the important parts explained. (Most of the tracks use a version of the lasagna exercise). This will be a quick introduction and the concepts will be explained in greater details later.
This will also include a quick introduction to Odin comments (single-line, multiple-lines, multiple-lines nested, and documentation comments) since it isn't worth dedicating a special concept to the subject.
Syntax covered: package, proc, constants, numbers and arithmetic operations, assignment, //, /* ... */, ///.
Aka: basics, 4db96bc0-433d-4a29-a1ec-45d37c86dd8d
Aka: packages, 79d17a9d-6758-40ac-947a-cbd699d8bc1e
Explain Odin’s packages, their relation to a directory, how and where to import packages from.
Explain that Exercism exercises are built with a Test Driven Development mindset.
Each exercise is organized as a (small) library, not a full program with a main() entry point.
Syntax covered: package, import,<package>.<proc>, "core:...".
Explain Odin’s integer and floating point types, their literals, and the limited auto-conversions. This will include a discussion of the arithmetic and bitwise operators.
Note: casting will have to be waved until we reach the Type Conversion concept.
Syntax covered: i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, int, uint, f16, f32, f64, type size and machine architecture, integer literals (w/ base 2, 8, 16), floating-point literals, +, -, ~, *, /, %, %%,
|, &, &~, <<, >>, compound operators (+=, ...).
Aka: numbers, 5e96a6b4-33dc-49d4-8f87-c1eaba0e3b49
Explain Odin’s boolean type, including boolean literals. This will also include a discussion of Odin's comparison and logic operators.
Syntax covered: b8, b16, b32, b63, bool, true, false, ==, !=, <, <=, >, >=, &&, ||, !.
Aka: booleans, 3d3396c6-ef2b-45bc-8c30-8080786e8dfc
Explain Odin’s Strings and Runes, the UTF-8 encoding, the unicode runes, and the string literals (including raw strings).
Explain that Odin’s strings have a built-in length and hint to the fact that Odin has a zero-terminated string type for interfacing with C (will be explained in Foreign System Interface).
Also warn about the error of using len() to count the number of characters in a string.
This should cover string formatting.
Note: there is also support for UTF-16 since this is Windows and JavaScript internal representation. Note sure how much we want to touch on that in this topic.
Syntax covered: string, string literal (including raw strings), rune, rune literal (w/ unicode notation), len(), +, "core:fmt", core:strings".
Aka: strings, 879d6d40-9e0a-4c38-97ce-b9fe5add1dc6
Explain Odin’s variable definitions and scope, including type inference, forced initialization to a zero value if one is not provided. This concept should also cover assignment. Odin doesn’t have a concept of immutable variables like Rust and other modern languages.
Note: Odin doesn't allow variables shadowing in nested scopes which may be surprising to some.
Syntax covered: <name>:[type]=[value], <name>=value.
Aka: variables, 66468f96-2ae5-43a8-a11c-65a0a26cbf05
Explain Odin’s constant as a compile-time artifact (not an immutable runtime value). Explain that Odin constants support type inference and use Untyped Types. Explain how Untyped Types are coerced to concrete types when a constant is assigned.
Syntax covered: <name>:[type]:value, value can be numbers, booleans, strings, procedures, composite type literals, untyped types.
Aka: constants, ffcb8c2e-5471-405f-beeb-58afeebe29c6
Describe Odin conditional control flows with the if-then-else and switch-case statements.
Explain that there are no parenthesis around the conditions but that curly braces are always required for the body.
Note: There is the do statement when the body of the conditional statement is a single expression, I have the impression that this is being de-emphasized in modern Odin but if we skip it and the user encounter it, they may be confused.
Explain how switch statements on enumeration types must cover all the cases or use a #partial directive.
Explain the default switch case (case:).
Syntax covered: if-else if-else, switch-case, #partial, <val_true> if <cond> else <val_false>, <cond> ? <val_true> : <val_false>.
Aka: conditionals, a4a8efb6-8cae-4ef3-9f35-c4ce0a6aeaeb
Discuss the syntax of pointers and pointers dereference in Odin as well as the notation for address.
Discuss how they can be used in dynamic containers, procedure parameters and to get dynamic memory.
Explain that Odin doesn’t support pointer arithmetic, including pointer indexing ([]).
Note: Odin has a type supporting pointer indexing (multi-pointer [^]<type>) but this is used by the Foreign System Interface and should be discussed there.
Note: This concept was moved up so we can discuss passing parameters by reference in the "Procedures" concept and explain new() and free() in the "Memory Management" concept below.
Syntax covered: ^<type>, <value>^, &<value>, uintptr, rawptr(may belong in the ffi concept).
Aka: pointers, 5212b8c1-c8ff-455c-a458-3c34725d9700
Explain how Odin’s “functions” are called procedures, describe the syntax of the procedures, the fact that they can be nested, and that Odin doesn't support closures.
Explain how parameters are passed by-value but can also be passed by reference (which is why we moved the "Pointer" concept up).
Explain how a procedure can have multiple return values but how this is different from a tuple type (not supported by Odin).
Syntax covered: <name> :: proc(<params>) -> (<return types>), return, anonymous procedures, nested procedures.
Aka: procedures, ba5631e3-b6ee-4fe8-9179-0fc68753ac3f
Explain how Odin has only for loops but the for can represents a C-like statement (with initialization, condition, and increment), a for-in (on a container type), a while loop (if only a condition is provided), or an infinite loop.
Describe how to get out of a loop early using break or continue and the use of label to get out of multiple nested loops at once.
Explain that, when iterating on a container type with for-in, you get a copy of the element and any change will not be reflected in the original element, unless you iterate on the address of the elements (for &element in container { …}).
Syntax covered: for i:=0; i < N; i +=1 {...}, for element in list {...}, for i in 0..<N {...}, for {...}, for <cond> { ...}, break [<label>], continue [<label>], <label>:.
Aka: loops, 1992331e-0e01-4d14-8ad2-8e807c595f77
Odin, like 99% of the languages supports recursion (but not tail-recursion). Is this worth presenting as a separate topic? For readers who are beginners, that may be good.
No new syntax.
Aka: recursion, e2c3169a-8c08-45f5-b5d9-a3478e1cbfe4
Aka: parameters, 61a3bba1-4e83-4a88-ba8c-5a5ba93a332d
Explain how we can setup default values for some of the parameters of a procedure. Show that the parameters can be called out of order if associated with their names.
Explain how we can have named return values and that they are initialized to their type zero value.
Explain that a procedure with named return values can use a naked return and that a return is required at the end of the procedure.
Syntax covered: default parameters, named return values, naked return.
Explain Odin’s syntax for fixed sized arrays. Explain that the arrays carry their own size and that a lot of the arithmetic operators can be applied to arrays. Discuss multi-dimensional arrays.
Syntax covered: [<N>]<type>, [?]{...}, [<N>]<type>{...}, for in.
Aka: arrays, 5bce87f3-b3c6-45f3-94a9-a3fbc3013ba5
Describe the struct as the main product type for Odin.
Describe how to create struct literals and how you can omit the type prefix if the compiler knows which type is needed (variable assignments or procedure parameters).
Note: it may be worth discussing here that Odin is an imperative language (not an OO language) and doesn't support methods.
The fact that Odin supports v-tables for interfacing with C and C++ software.
That should be deferred to the Foreign Function Interface section.
Syntax covered: <name> :: struct {...}.
Aka: structs, e1b28ab8-c966-4682-ad75-04c21d333c12
Describe Odin’s enumeration type and its relation to integer values (that may require the concept of type conversion which is lower in the list).
Syntax covered: <name> :: enum {...}, switch on enum.
Aka: enums, a8cb857f-d94a-4608-b6c4-55c892883beb
Explain that unions are Odin’s main sum type and that they carry an implicit tag (unlike C unions). Describe how you can use a switch statement to process the different types included in the union.
Note: you can get a C union (no tag) with the #raw_union directive on a struct.
Again this should be deferred to the Foreign Function Interface.
Syntax covered: <name> :: union {...}, switch on union.
Aka: unions, f2dda6a2-7cd1-464d-975a-571b9b0d8b29
Explain how Odin doesn’t support exception but can use any type as error type (often enums or unions but this needs to wait for these concepts). This is supported by the capability to return multiple values with the last one often being an error.
Show examples of using an union to represent all the errors reported by a procedure.
Syntax covered: since errors are types, there is no special syntax to introduce here.
Aka: errors, 1d25068d-8760-45e0-bfe4-cd9c537ac470
Discuss how you can index Odin’s fixed arrays with an enumerations and how this can be used as a “kind of static” map or with switch statements.
Syntax covered: <name> :: [<enum type>]<type>
Aka: enum-arrays, 52b4234e-2238-446d-b2be-f1d844eb581f
Explain how Odin can model finite sets of elements using a Bit Set with the elements being an integer range, a character range, or an enumeration. Discuss how the set behaves like a mathematical set (no duplicate elements) and list the different operators that act on sets and how to add/remove elements from the set.
Syntax covered: bit_set[<enum type>], bit_set[<range>], in, not_in, +=, -=, &, |, +, -.
Aka: bit-sets, 99f9d844-08bd-4bd2-a66c-6c8d9adad1b0
Explain how the context is an implicit parameter passed to a procedures by its caller.
Explain that the context includes the general and temp allocator (to be discussed later) but can also contain other data.
Show an example with other data.
Note: After some struggles, I moved this concept forward so it is before the concept on allocators. (I may change my mind again but I think it leaves too many things unsaid if presented after memory management). This should be a really short chapter, just introducing the fact that each call has an implicit parameter, copied down but not back up. Another solution would be to combine the Context concept with the Memory Management concept but I am afraid this would be too much to digest in one exercise.
Syntax covered: context and content of context (allocator, temp_allocator, assertion_failure_proc, logger, random_generator, user_ptr, user_index).
Aka: context, 6c451ee0-0def-45a2-a09d-07fde884c901
Discuss Odin manual memory management through allocators. Explain that Odin provides different allocators to group dynamic variables with the same lifetime in the same allocator so that they can be deallocated together. Introduce the arena allocator. Describe how procedures that dynamically allocate memory should take an allocator parameter (often defaulted)
Discuss how the context provides two allocators: the default allocator and the temp allocator and how they can be manipulated. Also show that they are typically used to provide default values to allocator parameters.
Introduce the temporary allocator and free_all(), explain how this is useful for a loop or game Frame.
Note: it is possible to go too far down this rabbit hole, we need to stay off the main path here. We can point to GingerBill excellent set of articles on memory allocators for additional reading.
Explain the topic of new() and free() for pointer types and broach the subject of make() and delete() for built-in types containing pointers (to be discussed later with maps, dynamic arrays, and slices).
Syntax covered: new(), free(), make(), destroy(), free_all(), arena.
Aka: memory-management, 19f17b53-909d-42fa-89a9-cc4b2bac82a3
Explain that the defer statement allows us to define code that is always executed when the current scope exits. Show how this allows us to group statement allocating and freeing resources close together making it easier to keep track of allocations and deallocations.
Syntax covered: defer.
Aka: defer, 8e3a2406-d74f-4ae3-8a0f-24b54803d67c
Explain how slices are a facade for arrays and how they allow procedures to be called on arrays of any size.
Show how to allocate a slice with make(), release it with delete() and how to use append() to grow them.
Explain that we can loop on slices like on arrays and that multiple slices defined on the sam array will alias that array.
Syntax covered: []<type>, <value>[<start index>:<stop index>], [:], append(), make(), delete(), for in.
Aka: slices, ada74db4-af74-41b7-bd1d-06f104a91e02
Describes the syntax for Odin’s dynamic array.
Explain how to allocate and free them, and how to use append() to add elements.
Discuss the notion of length vs. capacity.
Note: It is not always clear why we need dynamic arrays since we have slices that we can back with a memory segment. We may need to do a little bit more research to explain to the student when to use a dynamic array and when to use a slice.
Syntax covered: [dynamic]<type>, append(), make(), delete().
Aka: dynamic-arrays, ff729831-76fa-45b4-be37-c73a0e226e01
Explain the built-in map type, how it is defined, allocated, and freed. Explain how to check if a key is in the map and how to iterate on all (key, value) pairs.
Syntax covered: map[<key type>]<value type>, <map name>[<key>], for in.
Aka: maps, 7c5ee7f5-48bd-4c1c-930a-9a0a19d794b8
Explain the different types of cast syntax in Odin and how this can be used with numeric types and enums.
Explain the transmute() operator as a blind cast between types of the same size.
Syntax covered: cast(<type>)<value>, type(<value>), transmute(<type>)<value>
Aka: casts, aa9965bd-1e5a-4923-8be5-e88858f6fe83
Explain how procedures are first class in Odin and how they can be passed as arguments to other procedures, stored in variables and composite types. They can also be used as return values but, since Odin, doesn’t support closures, there are of limited usefulness in this case.
We could describe the concept of vtables in Odin and the special syntax associated with calling methods in tables (->).
This is an advanced topic so may be better split and discussed at a lower point in the concept tree (with Foreign Function Interface).
No new Syntax in this concept.
Aka: first-class-procedures, 90cc1b49-bb8a-4c25-b0d4-7e5e06820b8d
Introduce Odin’s syntax to define compile-time values and type parameters that can be used to generalize types and procedures. Show how to use generic procedures and data structures and how the compiler can infer the type (most of the time).
Syntax covered: $, typeid, where clause, specialization ($T/[dynamic]$E).
Aka: generics, afdb4afd-f262-47e3-b0f0-f26ea91cd32b
Explain how the using statement can be used with struct to implement composition (type nesting) and how the including type can access the fields and procedures associated with the “nested” type.
Syntax covered: using.
Aka: composition, 98cea22f-cade-47a6-bd94-f6f13983421c
Discuss the or_else, or_return, or_continue, and or_break operators and how they can be used to simplify Error Handling.
Explain how the first two requires procedures using them to use named return values.
Syntax covered: or_else, or_return, or_continue, or_break.
Aka: or-statements, 81758e49-a550-4b5e-b1bc-d6c8213910cc
Explain Odin’s syntax for variadic parameters, how slice values can be passed as a set of variadic parameters and give an example of a variadic procedure where the variadic argument is passed down to a nested variadic procedure (use fmt.printf(…) as the nested procedure).
Syntax covered: ..<type>, ..<value>, for in
Aka: variadic-parameters, 46cc7e5d-0e1c-47f3-9e65-36ece4bffc8f
Describe how Odin doesn’t support implicit procedure overloading but allow for explicit overloading.
Syntax covered: <name> :: proc {...}
Aka: explicit-overloading, c9062752-fc34-46db-b112-dc3a413867c8
Describe the matrix type and matrix operations, and explains how the compiler uses SIMD instruction for matrix operations.
Syntax covered: matrix[<N>,<M>], complexN, quaternionN, #simd[<N>]<type>.
Aka: matrix, 58a82ae3-46bb-4b21-bfc9-4f1c40803443
Explain how data-oriented programming often requires arrays of struct to be converted to struct of arrays to avoid cache misses.
Show how Odin provides a simple directive to achieve this while leaving the programmer to deal with the more common array of struct syntax.
Syntax covered: #soa.
Aka: structure-of-arrays, cc399bab-d16a-4302-86e9-6e45310b5039
Show how Odin supports conditional compilation with when to support multiple platforms.
Note: we may need to explain how Odin defines library dependencies straight in the source code.
Note: I am not sure how much of that can be exercised in the context of Exercism test-runner infrastructure.
Aka: conditional-compilation, 30d11f54-af0a-4299-b374-50982b02c836
Explain how Odin’s supports interfacing with any library that has a C ABI compatible interface. Show a simple example of calling either a user defined library or a function of the C library (probably posix to be available on the test-runner platform).
Shows how Odin has C specific types that must be used for interfacing with C libraries (especially zero-terminated cstring)
Explain how Odin let you specify the coding convention for an interface function.
Syntax covered: foreign import, calling conventions, c types (including cstring), "core:c".
Aka: ffi, aaf2e00b-ae08-4924-9d92-f1e25201ae68
Explain how Odin let you specify the bit allocation of a struct within a segment of memory and how this can be used to specify exact memory layout when dealing with external interfaces.
Syntax covered: <name> :: bit_field <type> {...}.
Aka: bit-fields, 55ee8e37-cc62-4ea5-af46-1e16193bb1b5
Explain the difference between attributes (@) and directives (#) and introduce a couple of the most useful ones.
Syntax covered: @(<attribute>), #directive.
Aka: attributes-directives, 24074068-85a2-4884-9aa2-830c5077bd84
Show how to use the tracking allocator to check for memory leaks and incorrect releases in your program.
Syntax covered: mem.Tracking_Allocator.
Aka: memory-tracking, 649e39bb-db76-4058-945d-c4ffa813c45a
Aka: testing, e66f615c-9485-49e7-9cbf-6e804e265100
Describe Odin’s facilities to write and run tests.
Discuss the fact that odin test run with the tracking allocator turned on and report memory leaks and multiple de-allocation for any code used in the tests.
Syntax covered: "core:testing", testing.expect...()
Aka: regex, da6ef497-b0e8-49f3-9a58-0f969f315bee
This is really a library but very useful for any real-life work.
Describes Odin regex package and how to use it.
Syntax covered "core:text/regex"
Explain how Odin supports concurrency via thread and synchronization objects.
Syntax covered: "core:thread", "core:sync".
Aka: concurrency, c0234788-2918-4e94-9d40-ceaf3856d30a