πΎ Archived View for auragem.space βΊ odin βΊ docs βΊ faq.gmi captured on 2022-04-29 at 11:23:12. Gemini links have been rewritten to link to archived content
β¬ οΈ Previous capture (2021-12-17)
-=-=-=-=-=-=-
The project started one evening in late July 2016 when Ginger Bill was annoyed with programming in C++. The language began as a Pascal clone (with `begin` and `end` and more) but changed quite quickly to become something else.
Bill originally tried to create a preprocessor for C to augment and add new capabilities to the language. However, he found this endeavour a dead-end. That evening was the point at which Bill decided to create an entirely new language from scratch instead of trying to augment C.
The language borrows heavily from (in order of philosophy and impact): Pascal, C, Go, Oberon.
Niklaus Wirth[1] and Rob Pike[2] have been the programming language design idols throughout this project.
1: https://en.wikipedia.org/wiki/Niklaus_Wirth
2: https://en.wikipedia.org/wiki/Rob_Pike
The Odin compiler and the library are under the BSD 2-Clause license[3].
3: https://github.com/odin-lang/Odin/blob/master/LICENSE
Check out a few selected libraries at https://github.com/odin-lang/odin-libs[4].
4: https://github.com/odin-lang/odin-libs
A quick overview of features (in no particular order):
And lots more!
5: /docs/overview/#defer-statement.gmi
6: /docs/overview/#branch-statements.gmi
7: /docs/overview/#parametric-polymorphism.gmi
8: /docs/overview/#foreign-system.gmi
9: /docs/overview/#when-statement.gmi
Odin was designed with the ideas of simplicity, clarity, speed of compilation, orthogonality of concepts, and for high performance. Feature X may be missing because it does not meet these basic ideas.
If you honestly believe that Odin is missing feature X, please investigate what features Odin does have and you may be able to find intriguing ways to solve your problem in the lack of feature X.
Coupling exceptions to a control structure, as in the try-catch-finally idiom, complicates the understanding of the program.
Odin uses plain error handling through the use of multiple return values. It is clear which procedure the error value is from compared to a `try-catch` approach which is akin to the COMEFROM[10] statement.
10: https://en.wikipedia.org/wiki/COMEFROM
Please see gingerBill's article for more information: Exceptions β And Why Odin Will Never Have Them[11].
11: https://www.gingerbill.org/article/2018/09/05/exceptions-and-why-odin-will-never-have-them/
No. Data structures as just data.
Subtype polymorphism is possible through the use of `using` but Odin does not offer methods.
Base :: struct {...}; Derived :: struct { using base: Base, name: string, } Derived_By_Ptr :: struct { name: string, // `using` can be applied anywhere and even to a pointer. // This allows for a huge amount of control over the memory // layout of the data structure. using base: ^Base, }
This idiom allows the user to create a virtual procedure table if they do wish, akin to C, but in a nicer way by having more control over the memory layout and field access.
The main reason is that it does not make any sense in Odin.
It is not "uniform" as Odin does not have the concept of a method. Odin also has the concept of import names for packages: this means procedures are declared within different scopes, meaning it would not make any sense syntactically.
One of Odin's goals is simplicity and striving to only offer one way to do things, to improve clarity. `x.f(y)` meaning `f(x, y)` is ambiguous as `x` may have a field called `f`. It is not at all clear what this means.
Very early on during Odin's development (circa. 2017), infix and suffix syntax for procedure calls was experimented with, but they were soon removed as it was found in practice that they were virtually never used nor did they actually aid with code clarity either.
One of the main reasons people want UFCS is to allow for "dot-autocomplete" in many IDEs, allowing the ability to show a list available procedures, dependent on the context. It is entirely possible to have this with normal procedure call syntax but most IDEs just do not do it for whatever reason.
The design goals of Odin were explicitness and simplicity. Implicit procedure overloading complicates the scoping system. In C++, you cannot nest procedures within procedures so all procedure look-ups are done at the global scope. In Odin, procedures can be nested within procedures and as a result, determining which procedure should be used, in the case of implicit overloading, is complex.
Explicit overloading has many advantages:
foo :: proc{ foo_bar, foo_baz, foo_baz2, another_thing_entirely, };
The design goals of Odin were explicitness and simplicity. Operator overloading is very easily abused and can be used to do many magical things. A procedure is clearer and more explicit.
Array programming is available in Odin; this removes some of the need for operator overloading when creating mathematical libraries.
`distinct` makes a type declaration distinct from its base type.
Int_Alias :: int; #assert(Int_Alias == int); My_Int :: distinct int; #assert(My_Int != int);
`123` is an untyped integer literal: if the type has not been specified in the declaration, the default type for the "untyped" type will be chosen. In this case, `x` will be of type `int`.
ββββββββββββββββββββββββββ¬βββββββββββββββ β Untyped type β Default Type β ββββββββββββββββββββββββββͺβββββββββββββββ‘ β boolean β `bool` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β integer β `int` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β float β `f64` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β complex β `complex128` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β rune β `rune` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β string β `string` β ββββββββββββββββββββββββββΌβββββββββββββββ€ β nil β * β ββββββββββββββββββββββββββΌβββββββββββββββ€ β undef β * β ββββββββββββββββββββββββββΌβββββββββββββββ€ β type (not first class) β * β ββββββββββββββββββββββββββ΄βββββββββββββββ
`size_of(int) = size_of(uint) = size_of(rawptr)`. For portability, code that relies on a particular size of value should use an explicitly sized type, like `i64`. `int` and `uint` are guaranteed to be big enough to represent a pointer; however, please use `uintptr` to represent a pointer.
Floating-point types and complex types are always sized, because the programmer should be aware of precision.
The choice is dependent on the purpose of the program. The default floating point type is `f64` so if in doubt, prefer `f64`.
A `rune` is a basic type that is used to represent individual Unicode code points. It is equivalent to an `i32` internally but they are not the same type.
Character literals such as `'g'`, `'θ'`, and `'\u0123'` are all untyped runes, with the default type `rune`. Character literals can be used as numbers and can convert to any number type.
Implicit conversions complicate things and would be difficult to make consistent across architectures. To increase portability and to simplify the language, Odin uses explicit conversion.
In C, the confusion caused by implicit numeric type conversions is outweighed by the convenience it provides. There are many rules in C which are not at all obvious nor simple to the reader of the code (e.g. is this expression unsigned does this expression over/under-flow? etc).
The exceptions to this being all pointer types can automatically coerce to a `rawptr` untyped constants can be convert to a type if that conversion is valid. The constant system does reduce a lot of the issues regarding types as "numbers just work"; there is no need for literal suffixes like in C.
Most programmers spend most of their time *reading* code; not *writing* code. And that has been a big design in Odin: making it clear to the reader what is going on in the program. In most cases Odin has been optimized for the *reader* of code more than the *writer* of code; as a result this can annoy the *writer* in certain cases when there requires a lot of explicit type conversions.
This is mostly because it "felt" right and is very convenient. Having them as values would require many allocations and may even require automatic memory management to handle correctly.
A procedure is a superset of functions and subroutines. A procedure may or may not return something. A procedure may or may not have side effects.
We believe that data and code should be separate concepts; data should not have "behaviour".
Use a procedure.
proc "c" () proc "cdecl" () proc "stdcall" () proc "fastcall" () proc "odin" () proc "contextless" () proc "none" ()
Odin only has non-capturing lambda procedures. For closures to work correctly would require a form of automatic memory management which will never be implemented into Odin.
foo :: proc() { y: int; x := proc() -> int { // `y` is not available in this scope as it is in a different stack frame return 123; }; }
In each scope, there is an implicit value named `context`. This `context` variable is local to each scope and is implicitly passed by pointer to any procedure call in that scope (if the procedure has the Odin calling convention).
The main purpose of the implicit context system is for the ability to intercept third-party code and libraries and modify their functionality. One such case is modifying how a library allocates something or logs something. In C, this was usually achieved with the library defining macros which could be overridden so that the user could define what he wanted. However, not many libraries supported this in many languages by default which meant intercepting third-party code to see what it does and to change how it does it is not possible.
Please see the overview section on the implicit context system[12] for more information.
12: /docs/overview/#implicit-context-system.gmi
In Odin, procedure parameters are immutable values. This allows Odin to optimize how procedure values are passed. If it is more efficient to pass them by value (making a copy) or more efficient to pass them as an immutable pointer internally, it does not matter from a user perspective as the parameter value is immutable (because of the Odin calling conventions).
Passing a pointer value makes a copy of the pointer, not the data it points to. Slices, dynamic arrays, and maps behave like pointers in this case (internally they are structures that contain values, which include pointers and the "structure" and may be passed as an immutable pointer internally for performance).
Originally, continuing the C family tradition, everything in Odin was passed by value. However, for performance reasons, this behaviour was changed for the Odin calling conventions.
`new` allocates memory.
ptr: ^int = new(int);
`make` initializes the slice, dynamic array, and map types.
slice: []int = make([]int, 16);
`free` deallocates memory
ptr: ^int = new(int); free(ptr);
`delete` deinitializes the the slice, dynamic array, map, and string types.
slice := make([]int, 10); delete(slice); m := make(map[int]string); delete(m); str: string = ...; delete(str);
Put all the `.odin` source files for a package in a directory. Source files must have the same `package` declaration. All source files within a package can refer to items from other files. There is no need for a forward declarations or a header file like in C.
The core library is not yet complete. However when it is complete, it will remain small, as its purpose is to support the runtime, operating system specific calls, formatted I/O, and other key functionality that most Odin programs require.
This is two different operators `:` and `=`; is used for variable declarations. The following are all equivalent:
x : int = 123; x : = 123; x := 123; x := int(123);
This is two different separate operators `:` and `:`; is used for constant value declarations.
X :: 123; X : : 123; Y : int : 123; Y :: int(123); Z :: proc() {}; Z : proc() : proc() {}; // Redundant type declaration
Please see gingerBill's article On the Aesthetics of the Syntax of Declarations[13].
13: https://www.gingerbill.org/article/2018/03/12/on-the-aesthetics-of-the-syntax-of-declarations/
cast(type)value type(value) or (type)(value)
The reason that there are two ways to do type conversions is because one approach may *feel* better than the other case. If you are converting a large expression, it sometimes a lot easier to use the operator-style approach, `cast(type)`. The call syntax is commonly used to specify a type of an expression which may be relatively short such as `u32(123)` or `uintptr(ptr)`.
There are two other type conversion operators, transmute[14] and auto_cast[15].
14: /docs/overview/#type-conversion.gmi
15: /docs/overview/#auto-cast-operation.gmi
Curly brackets to denote a block is a common approach in many programming languages, and Odin's consistency is useful for people already familiar with the style. Curly brackets also allow for more flexible syntax styles for the programmer and it is easier to parse by the compiler because it is not white space sensitive.
Semicolons are used to denote the termination of a statement. If semicolons where made optional, it would mean enforcing a coding style either through sensitive white space (Python-esque) or curly brace positioning (automatic semicolon insertion). With semicolons, the programmer is free to decide what style is best suited to his needs.
The compiler is written in C++ but in a very C style. For the current backend, LLVM is used to translate code to platform specific code. A custom backend is in development.
Other than the declaration syntax, the differences are minor. When designing the syntax, it had to feel right and light. A minimal amount of keywords and syntactic sugar. The syntax has been designed to be very easily to parse without a symbol table. This makes it easier to create build and analysis tools for Odin.
Declarations are only backwards if you are used to C. In C, declarations follow the "clockwise/spiral rule"[16] to reflect the usage of the declaration. This is can be confusing when reading.
16: http://c-faq.com/decl/spiral.anderson.html
In C, the declaration:
int *a, b;
declares `a` to be a "pointer to int" but b to be an "int"; in Odin
a, b: ^int;
declares both to be a "pointer to int". This is clearer and more regular. This syntax is borrowed from the Pascal family, along with using `^` to denote a pointer, as it is pointy.
Due to the style of value declarations, the type can be omitted and inferred from the declaration. The following are all equivalent:
a: int = 123; a : = 123; a := 123; a := int(123);
Type safety and simplicity. Due to slices being a first-class datatype, a lot of the need for pointer arithmetic is reduced. However, if you still require it, the `mem` package provides so utility functions: `mem.ptr_offset` and `mem.ptr_sub`. Odin will allow the programmer to do unsafe things if he wishes so.
Pre-increment and post-increment, and the decrement equivalents, look simple but are complex. They require knowledge of the evaluation order and lead to subtle bugs. `f(i++)` or `a[++i] = b[i]` are both confusing, even if the rules are well defined. Removing this is a significant simplification.
`x += 1;` is slightly longer but it is unambiguous.
No. The philosophy for Odin is that the zero value should be useful. By default, all variables are initialized to zero unless told otherwise with the `---` value.
x: int; // initialized to zero y: int = 0; // explicitly initialized to zero z: int = ---; // uninitialized memory
No. All copies are byte-for-byte copies.
No. There are no ownership semantics in Odin.
No. `defer` can be used to defer a statement till end of a scope. `defer` is explicit and much more flexible than a C++-style destructor in that it can be used for anything.
f, err := os.open(...); if err != os.ERROR_NONE { // handle error } defer os.close(f); // will be executed at the end of the scope ...
There is also the `deferred_*` attributes which can be attached to procedures to have very useful functionality, such as IMGUIs[17].
17: https://en.wikipedia.org/wiki/Immediate_Mode_GUI
Example:
begin_menu :: proc(name: string, flags: Flags = nil) -> (open: bool) { ... } end_menu :: proc(open := true) { if !open do return; ... } @(deferred_out=end_menu) menu :: proc(name: string, flags: Flags = nil) -> (open: bool) { return begin_menu(name, flags); } if begin_menu("Hello") { defer end_menu(); } if menu("Hello") { }
Odin is not currently self hosted nor will be until *after* version 1.0 when the main implementation of the Odin compiler adheres to a specification and is heavily tested. In general, self hosting before a stable language and compiler exists is masturbatory pleasure.
Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I wonβt usually need your flowcharts; theyβll be obvious.
A little retrospection shows that although many fine, useful software systems have been designed by committees and built as part of multipart projects, those software systems that have excited passionate fans are those that are the products of one or a few designing minds, great designers.
The language designer should be familiar with many alternative features designed by others, and should have excellent judgment in choosing the best and rejecting any that are mutually inconsistent... One thing he should not do is to include untried ideas of his own. His task is consolidation, not innovation.
The most important property of a program is whether it accomplishes the intention of its user
Most ideas come from previous ideas.
Reliable and transparent programs are usually not in the interest of the designer.
... we do not consider it as good engineering practice to consume a resource lavishly just because it happens to be cheap
Increasingly, people seem to interpret complexity as sophistication, which is baffling β the incomprehensible should cause suspicion, not admiration. Possibly this results from the mistaken belief that using a mysterious device confers [extra] power on the user.