mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2025-08-13 18:24:39 +02:00
[odin/en] Add Odin.md (#5346)
* Create odin.md * Add link to Odin book * Improve build-int data structure section * Fix os.open() example and remove SIMD
This commit is contained in:
575
odin.md
Normal file
575
odin.md
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
---
|
||||||
|
name: Odin
|
||||||
|
contributors:
|
||||||
|
- ["Collin MacDonald", "https://github.com/CollinEMac"]
|
||||||
|
filename: learnodin.odin
|
||||||
|
---
|
||||||
|
|
||||||
|
Odin was created by Bill "gingerBill" Hall. It is a general-purpose systems
|
||||||
|
programming language that emphasizes simplicity, readability, and performance
|
||||||
|
without garbage collection. Odin bills itself as "the C alternative for the
|
||||||
|
joy of programming."
|
||||||
|
|
||||||
|
```odin
|
||||||
|
// Single line comments start with two slashes.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Multiline comments start with slash-star,
|
||||||
|
and end with star-slash. They can be nested!
|
||||||
|
/*
|
||||||
|
Like this!
|
||||||
|
*/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is the classic "hello world" program in Odin.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
fmt.println("Hellope!")
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 1. Basic Data Types and Operators
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Integers - Odin has explicit sized integer types
|
||||||
|
x: i32 = 42 // 32-bit signed integer
|
||||||
|
y: u64 = 100 // 64-bit unsigned integer
|
||||||
|
z: int = 123 // Platform-dependent integer (usually i64)
|
||||||
|
|
||||||
|
// You can use underscores for readability in numbers
|
||||||
|
big_number := 1_000_000
|
||||||
|
|
||||||
|
// Floating point numbers
|
||||||
|
pi: f32 = 3.14159 // 32-bit float
|
||||||
|
e: f64 = 2.71828 // 64-bit float (default for float literals)
|
||||||
|
|
||||||
|
// Boolean
|
||||||
|
is_true: bool = true
|
||||||
|
is_false: bool = false
|
||||||
|
|
||||||
|
// Rune (Unicode character)
|
||||||
|
letter: rune = 'A'
|
||||||
|
emoji: rune = '🚀'
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
name: string = "Odin Programming"
|
||||||
|
raw_string := `C:\Windows\System32` // Raw string with backticks
|
||||||
|
|
||||||
|
// String length (in bytes, not characters!)
|
||||||
|
length := len(name)
|
||||||
|
|
||||||
|
// Arithmetic operators work as you'd expect
|
||||||
|
result := 10 + 5 // 15
|
||||||
|
diff := 10 - 5 // 5
|
||||||
|
product := 10 * 5 // 50
|
||||||
|
quotient := 10 / 5 // 2
|
||||||
|
remainder := 10 % 3 // 1
|
||||||
|
|
||||||
|
// Comparison operators
|
||||||
|
is_equal := 10 == 10 // true
|
||||||
|
not_equal := 10 != 5 // true
|
||||||
|
greater := 10 > 5 // true
|
||||||
|
less_equal := 5 <= 10 // true
|
||||||
|
|
||||||
|
// Logical operators
|
||||||
|
and_result := true && false // false
|
||||||
|
or_result := true || false // true
|
||||||
|
not_result := !true // false
|
||||||
|
|
||||||
|
// Bitwise operators
|
||||||
|
bit_and := 0b1010 & 0b1100 // 0b1000
|
||||||
|
bit_or := 0b1010 | 0b1100 // 0b1110
|
||||||
|
bit_xor := 0b1010 ~ 0b1100 // 0b0110 (note: ~ is XOR in Odin)
|
||||||
|
bit_not := ~0b1010 // bitwise NOT
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 2. Variables and Constants
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Variable declaration with type inference
|
||||||
|
some_number := 42 // Type inferred as int
|
||||||
|
some_text := "Hello" // Type inferred as string
|
||||||
|
|
||||||
|
// Explicit type declaration
|
||||||
|
explicit_int: int = 42
|
||||||
|
explicit_float: f64 = 3.14
|
||||||
|
|
||||||
|
// Uninitialized variables are zero-initialized
|
||||||
|
uninitialized_int: int // Defaults to 0
|
||||||
|
uninitialized_bool: bool // Defaults to false
|
||||||
|
uninitialized_string: string // Defaults to ""
|
||||||
|
|
||||||
|
// Constants are defined with ::
|
||||||
|
PI :: 3.14159
|
||||||
|
MESSAGE :: "This is a constant"
|
||||||
|
|
||||||
|
// Typed constants
|
||||||
|
TYPED_CONSTANT : f32 : 2.71828
|
||||||
|
|
||||||
|
// Multiple assignment
|
||||||
|
a, b := 10, 20
|
||||||
|
a, b = b, a // Swap values
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 3. Arrays and Slices
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Fixed-size arrays
|
||||||
|
numbers: [5]int = {1, 2, 3, 4, 5}
|
||||||
|
chars: [3]rune = {'A', 'B', 'C'}
|
||||||
|
|
||||||
|
// Array with inferred size
|
||||||
|
inferred := [..]int{10, 20, 30, 40}
|
||||||
|
|
||||||
|
// Zero-initialized array
|
||||||
|
zeros: [10]int // All elements are 0
|
||||||
|
|
||||||
|
// Accessing array elements
|
||||||
|
first := numbers[0] // 1
|
||||||
|
last := numbers[4] // 5
|
||||||
|
array_length := len(numbers) // 5
|
||||||
|
|
||||||
|
// Slices - dynamic views into arrays
|
||||||
|
slice: []int = {1, 2, 3, 4, 5} // Slice literal
|
||||||
|
array_slice := numbers[1:4] // Slice of array from index 1 to 3
|
||||||
|
full_slice := numbers[:] // Slice of entire array
|
||||||
|
|
||||||
|
// Dynamic arrays - can grow and shrink
|
||||||
|
dynamic_array: [dynamic]int
|
||||||
|
append(&dynamic_array, 1)
|
||||||
|
append(&dynamic_array, 2, 3, 4) // Append multiple elements
|
||||||
|
|
||||||
|
// Remember to clean up dynamic arrays
|
||||||
|
defer delete(dynamic_array)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 4. Control Flow
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// If statements
|
||||||
|
age := 25
|
||||||
|
if age >= 18 {
|
||||||
|
fmt.println("Adult")
|
||||||
|
} else if age >= 13 {
|
||||||
|
fmt.println("Teenager")
|
||||||
|
} else {
|
||||||
|
fmt.println("Child")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For loops - Odin's only loop construct
|
||||||
|
// C-style for loop
|
||||||
|
for i := 0; i < 10; i += 1 {
|
||||||
|
fmt.println(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// While-style loop
|
||||||
|
counter := 0
|
||||||
|
for counter < 5 {
|
||||||
|
fmt.println(counter)
|
||||||
|
counter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infinite loop
|
||||||
|
for {
|
||||||
|
// This runs forever (until break)
|
||||||
|
break // Exit the loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterating over arrays/slices with index
|
||||||
|
numbers_array := [3]int{10, 20, 30}
|
||||||
|
for value, index in numbers_array {
|
||||||
|
fmt.printf("Index %d: Value %d\n", index, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterating over just values
|
||||||
|
for value in numbers_array {
|
||||||
|
fmt.println(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch statements
|
||||||
|
day := "Monday"
|
||||||
|
switch day {
|
||||||
|
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
|
||||||
|
fmt.println("Weekday")
|
||||||
|
case "Saturday", "Sunday":
|
||||||
|
fmt.println("Weekend")
|
||||||
|
case: // Default case
|
||||||
|
fmt.println("Unknown day")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch with no condition (like if-else chain)
|
||||||
|
switch {
|
||||||
|
case age < 13:
|
||||||
|
fmt.println("Child")
|
||||||
|
case age < 20:
|
||||||
|
fmt.println("Teenager")
|
||||||
|
case:
|
||||||
|
fmt.println("Adult")
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 5. Procedures (Functions)
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Basic procedure definition
|
||||||
|
add :: proc(a: int, b: int) -> int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procedure with multiple return values
|
||||||
|
divide :: proc(a: int, b: int) -> (int, bool) {
|
||||||
|
if b == 0 {
|
||||||
|
return 0, false // Division by zero
|
||||||
|
}
|
||||||
|
return a / b, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the procedure
|
||||||
|
sum := add(5, 3) // 8
|
||||||
|
quotient, ok := divide(10, 2) // 5, true
|
||||||
|
quotient_bad, ok_bad := divide(10, 0) // 0, false
|
||||||
|
|
||||||
|
// Procedure with default parameters (using overloading)
|
||||||
|
greet :: proc(name: string) {
|
||||||
|
fmt.printf("Hello, %s!\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
greet :: proc() {
|
||||||
|
greet("World")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variadic procedures (variable number of arguments)
|
||||||
|
sum_all :: proc(numbers: ..int) -> int {
|
||||||
|
total := 0
|
||||||
|
for number in numbers {
|
||||||
|
total += number
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
result_sum := sum_all(1, 2, 3, 4, 5) // 15
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 6. Structs
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Struct definition
|
||||||
|
Person :: struct {
|
||||||
|
name: string,
|
||||||
|
age: int,
|
||||||
|
height: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating struct instances
|
||||||
|
person1 := Person{
|
||||||
|
name = "Alice",
|
||||||
|
age = 30,
|
||||||
|
height = 5.6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial initialization (remaining fields are zero-initialized)
|
||||||
|
person2 := Person{
|
||||||
|
name = "Bob",
|
||||||
|
// age and height default to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessing struct fields
|
||||||
|
fmt.printf("%s is %d years old\n", person1.name, person1.age)
|
||||||
|
|
||||||
|
// Modifying struct fields
|
||||||
|
person1.age = 31
|
||||||
|
|
||||||
|
// Procedure that works with structs
|
||||||
|
celebrate_birthday :: proc(person: ^Person) { // ^ means pointer
|
||||||
|
person.age += 1
|
||||||
|
fmt.printf("Happy birthday! %s is now %d\n", person.name, person.age)
|
||||||
|
}
|
||||||
|
|
||||||
|
celebrate_birthday(&person1) // Pass address with &
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 7. Enums and Unions
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Enums
|
||||||
|
Color :: enum {
|
||||||
|
RED,
|
||||||
|
GREEN,
|
||||||
|
BLUE,
|
||||||
|
YELLOW,
|
||||||
|
}
|
||||||
|
|
||||||
|
my_color := Color.RED
|
||||||
|
|
||||||
|
// Enums with explicit values
|
||||||
|
Status :: enum u8 {
|
||||||
|
OK = 0,
|
||||||
|
ERROR = 1,
|
||||||
|
WARNING = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unions (tagged unions)
|
||||||
|
Shape :: union {
|
||||||
|
Circle: struct { radius: f32 },
|
||||||
|
Rectangle: struct { width, height: f32 },
|
||||||
|
Triangle: struct { base, height: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
my_shape := Shape(Circle{{radius = 5.0}})
|
||||||
|
|
||||||
|
// Pattern matching with unions
|
||||||
|
switch shape in my_shape {
|
||||||
|
case Circle:
|
||||||
|
fmt.printf("Circle with radius %.2f\n", shape.radius)
|
||||||
|
case Rectangle:
|
||||||
|
fmt.printf("Rectangle %.2f x %.2f\n", shape.width, shape.height)
|
||||||
|
case Triangle:
|
||||||
|
fmt.printf("Triangle base %.2f, height %.2f\n", shape.base,
|
||||||
|
shape.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 8. Maps
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Map declaration
|
||||||
|
scores: map[string]int
|
||||||
|
|
||||||
|
// Initialize map
|
||||||
|
scores = make(map[string]int)
|
||||||
|
defer delete(scores) // Clean up when done
|
||||||
|
|
||||||
|
// Add key-value pairs
|
||||||
|
scores["Alice"] = 95
|
||||||
|
scores["Bob"] = 87
|
||||||
|
scores["Charlie"] = 92
|
||||||
|
|
||||||
|
// Access values
|
||||||
|
alice_score := scores["Alice"] // 95
|
||||||
|
|
||||||
|
// Check if key exists
|
||||||
|
bob_score, exists := scores["Bob"]
|
||||||
|
if exists {
|
||||||
|
fmt.printf("Bob's score: %d\n", bob_score)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over map
|
||||||
|
for name, score in scores {
|
||||||
|
fmt.printf("%s: %d\n", name, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map literal
|
||||||
|
ages := map[string]int{
|
||||||
|
"Alice" = 30,
|
||||||
|
"Bob" = 25,
|
||||||
|
"Charlie" = 35,
|
||||||
|
}
|
||||||
|
defer delete(ages)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 9. Pointers and Memory Management
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Pointers
|
||||||
|
number := 42
|
||||||
|
number_ptr := &number // Get address of number
|
||||||
|
value := number_ptr^ // Dereference pointer (get value)
|
||||||
|
|
||||||
|
fmt.printf("Value: %d, Address: %p\n", value, number_ptr)
|
||||||
|
|
||||||
|
// Dynamic memory allocation
|
||||||
|
// new() allocates and returns a pointer
|
||||||
|
int_ptr := new(int)
|
||||||
|
int_ptr^ = 100
|
||||||
|
defer free(int_ptr) // Clean up memory
|
||||||
|
|
||||||
|
// make() for complex types
|
||||||
|
my_slice := make([]int, 5) // Slice with length 5
|
||||||
|
defer delete(my_slice)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 10. Error Handling
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Odin uses multiple return values for error handling
|
||||||
|
read_file :: proc(filename: string) -> (string, bool) {
|
||||||
|
// Simulate file reading
|
||||||
|
if filename == "" {
|
||||||
|
return "", false // Error case
|
||||||
|
}
|
||||||
|
return "file contents", true // Success case
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the error-returning procedure
|
||||||
|
content, success := read_file("myfile.txt")
|
||||||
|
if !success {
|
||||||
|
fmt.println("Failed to read file")
|
||||||
|
} else {
|
||||||
|
fmt.printf("File content: %s\n", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common pattern with or_return
|
||||||
|
parse_number :: proc(s: string) -> (int, bool) {
|
||||||
|
// This is a simplified example
|
||||||
|
if s == "42" {
|
||||||
|
return 42, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
example_with_error_handling :: proc() -> bool {
|
||||||
|
// or_return automatically returns false if the second value is false
|
||||||
|
num := parse_number("42") or_return
|
||||||
|
fmt.printf("Parsed number: %d\n", num)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 11. Packages and Imports
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Every .odin file starts with a package declaration
|
||||||
|
// package main // (Already declared at the top)
|
||||||
|
|
||||||
|
// Import from core library
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
// Import with alias
|
||||||
|
import str "core:strings"
|
||||||
|
|
||||||
|
// Using imported procedures
|
||||||
|
text := "Hello, World!"
|
||||||
|
upper_text := strings.to_upper(text)
|
||||||
|
fmt.println(upper_text)
|
||||||
|
|
||||||
|
// Import from vendor packages (external libraries)
|
||||||
|
// import "vendor:raylib"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 12. Compile-time Features
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Compile-time conditionals
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
// Windows-specific code
|
||||||
|
fmt.println("Running on Windows")
|
||||||
|
} else when ODIN_OS == .Linux {
|
||||||
|
// Linux-specific code
|
||||||
|
fmt.println("Running on Linux")
|
||||||
|
} else {
|
||||||
|
// Other platforms
|
||||||
|
fmt.println("Running on other platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time constants
|
||||||
|
ODIN_DEBUG :: #config(DEBUG, false)
|
||||||
|
|
||||||
|
when ODIN_DEBUG {
|
||||||
|
fmt.println("Debug mode enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generics (Parametric polymorphism)
|
||||||
|
Generic_Array :: struct($T: typeid) {
|
||||||
|
data: []T,
|
||||||
|
}
|
||||||
|
|
||||||
|
max :: proc(a: $T, b: T) -> T {
|
||||||
|
return a if a > b else b
|
||||||
|
}
|
||||||
|
|
||||||
|
max_int := max(10, 20) // T becomes int
|
||||||
|
max_float := max(3.14, 2.71) // T becomes f64
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 13. Built-in Data Structures
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Bit sets for flags
|
||||||
|
File_Mode :: enum {
|
||||||
|
READ,
|
||||||
|
WRITE,
|
||||||
|
EXECUTE,
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions: bit_set[File_Mode]
|
||||||
|
permissions |= {.READ, .WRITE} // Set multiple flags
|
||||||
|
permissions &~= {.WRITE} // Remove flag
|
||||||
|
has_read := .READ in permissions // Check flag
|
||||||
|
is_readonly := permissions == {.READ} // Compare sets
|
||||||
|
|
||||||
|
// Complex numbers
|
||||||
|
z1 := complex64(3 + 4i)
|
||||||
|
z2 := complex64(1 - 2i)
|
||||||
|
sum := z1 + z2 // (4 + 2i)
|
||||||
|
magnitude := abs(z1) // 5.0
|
||||||
|
|
||||||
|
// Matrices for linear algebra
|
||||||
|
transform := matrix[3, 3]f32{
|
||||||
|
1, 0, 5, // Translation X = 5
|
||||||
|
0, 1, 3, // Translation Y = 3
|
||||||
|
0, 0, 1, // Homogeneous coordinate
|
||||||
|
}
|
||||||
|
|
||||||
|
point := [3]f32{10, 20, 1}
|
||||||
|
transformed := transform * point // Matrix multiplication
|
||||||
|
|
||||||
|
// Quaternions for 3D rotations
|
||||||
|
identity_rot := quaternion128{0, 0, 0, 1} // No rotation
|
||||||
|
rotation_90_z := quaternion128{0, 0, 0.707, 0.707} // 90° around Z
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
## 14. Context System and Defer
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Odin has an implicit context system for threading allocators,
|
||||||
|
// loggers, and other utilities through your program
|
||||||
|
|
||||||
|
example_with_context :: proc() {
|
||||||
|
// Save current context
|
||||||
|
old_allocator := context.allocator
|
||||||
|
|
||||||
|
// Use a different allocator temporarily
|
||||||
|
temp_allocator := context.temp_allocator
|
||||||
|
context.allocator = temp_allocator
|
||||||
|
|
||||||
|
// All allocations in this scope use temp_allocator
|
||||||
|
temp_data := make([]int, 100)
|
||||||
|
// No need to delete temp_data - it's automatically cleaned up
|
||||||
|
|
||||||
|
// Restore original allocator
|
||||||
|
context.allocator = old_allocator
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer ensures cleanup happens when scope exits
|
||||||
|
resource_management_example :: proc() {
|
||||||
|
file_handle := os.open("example.txt", os.O_RDONLY, 0) or_return
|
||||||
|
defer os.close(file_handle) // Always closed when function exits
|
||||||
|
|
||||||
|
buffer := make([]u8, 1024)
|
||||||
|
defer delete(buffer) // Always freed when function exits
|
||||||
|
|
||||||
|
// Use file_handle and buffer...
|
||||||
|
// They're automatically cleaned up even if we return early
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
The [Odin Programming Language website](https://odin-lang.org/) provides
|
||||||
|
excellent documentation and examples.
|
||||||
|
|
||||||
|
The [overview](https://odin-lang.org/docs/overview/) covers much of the
|
||||||
|
language in detail.
|
||||||
|
|
||||||
|
The [GitHub examples repository](https://github.com/odin-lang/examples)
|
||||||
|
contains idiomatic Odin code examples.
|
||||||
|
|
||||||
|
For learning resources:
|
||||||
|
- ["Understanding the Odin Programming Language" by Karl Zylinski](https://odinbook.com)
|
||||||
|
- [Odin Discord](https://discord.gg/sVBPHEv) for community support
|
||||||
|
- [FAQ](https://odin-lang.org/docs/faq/) for common questions
|
Reference in New Issue
Block a user