Procedures

  • Procedure  used to be the common term as opposed to a function or subroutine. A function is a mathematical entity that has no side effects. A subroutine is something that has side effects but does not return anything.

  • 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.

multiply :: proc(x: int, y: int) -> int {
    return x * y
}
fmt.println(multiply(137, 432))
multiply :: proc(x, y: int) -> int {
    return x * y
}
fmt.println(multiply(137, 432))
  • Everything in Odin is passed by value, rather than by reference.

  • All procedure parameters in Odin are immutable values.

  • 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” is passed by value).

Calling Conventions

  • Procedure types are only compatible with the procedures that have the same calling convention and parameter types.

odin
  • By default, Odin procedures use the "odin"  calling convention.

  • This calling convention is the same as C, however it differs in a couple of ways:

    • It promotes values to a pointer if that’s more efficient on the target system

      • Where would this be more efficient?

      • It passes all parameters larger than 16 bytes  by reference.

      • The promotion is enabled by the fact that all parameters are immutable in Odin, and its rules are consistent for a given type and platform and can be relied on since they are part of the calling convention.

      • Passing a pointer value makes a copy of the pointer, not the data it points to. Slices, dynamic arrays, and maps have no special considerations here; they are normal structures with pointer fields, and are passed as such. Their elements will not be copied.

      • Note: This is subject to change.

    • It includes a pointer to the current context as an implicit additional argument .

contextless
  • Same as odin  but without the implicit context  pointer.

stdcall / std
  • This is the stdcall  convention as specified by Microsoft.

c / cdecl
  • This is the default calling convention generated of a procedure in C.

  • If it's within a foreign  block, the default calling conventions is cdecl .

fastcall / fast
  • This is a compiler dependent calling convention.

none
  • This is a compiler dependent calling convention which will do nothing to parameters.

Variadic Arguments

  • Ginger Bill: "It's just a slice allocated on the stack."

    foo :: proc(x: ..int) {} 
    
    // Calling
    foo(1, 2, 3)
    
    // is the same as
    temp_array := [3]int{1, 2, 3} 
    temp_slice := temp_array[:] 
    foo(..temp_slice)
    
  • Procedures can be variadic, taking a varying number of arguments:

sum :: proc(nums: ..int) -> (result: int) {
    for n in nums {
        result += n
    }
    return
}
fmt.println(sum())              // 0
fmt.println(sum(1, 2))          // 3
fmt.println(sum(1, 2, 3, 4, 5)) // 15

odds := []int{1, 3, 5}
fmt.println(sum(..odds))        // 9, passing a slice as varargs

Multiple returns

swap :: proc(x, y: int) -> (int, int) {
    return y, x
}
a, b := swap(1, 2)
fmt.println(a, b) // 2 1
  • Implicitly :

    end_msg_as_bytes, err_end := cbor.marshal_into_bytes(end_msg)
    
  • Explicitly :

    end_msg_as_bytes: []byte
    err_end: cbor.MarshalError
    end_msg_as_bytes, err_end = cbor.marshal_into_bytes(end_msg)
    
    // or
    packet_as_bytes: []byte; err_packet: cbor.Marshal_Error
    

    packet_as_bytes, err_packet = cbor.marshal_into_bytes(packet[:])
```

Closures (They don't exist)

  • Does not have closures, only Lambdas.

  • 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
    }
}

Procedure Groups (explicit overload)

  • Explicit Procedure Overloading .

  • Caio:

    • if I have a struct that inherits another struct with using , and then I make a procedure group, where the first procedure accepts the original struct, and the second accepts the struct that inherits the first struct, what would happen? This "higher level" struct would call which of these procedures? Does it depend on the order the procedures are stored in the procedure group, or something like that? Casting has been the weirdest thing for me.

  • Barinzaya:

    • The order of the procs in the proc group isn't used to decide which to call, the compiler "scores" each candidate to decide which one is the best fit for a given call. As best I can tell it does  appear that the compiler accounts for subtypes when doing this, so it should consistently call the proc closest to the base type https://github.com/odin-lang/Odin/blob/090cac62f9cc30f759cba086298b4bdb8c7c62b3/src/check_expr.cpp#L829.

  • Odin:

    • In retrospect it sounds a bit weird that odin checks for subtyping in cases of proc groups, but it can't be done directly. In a way, overloading itself sounds weird with no RTTI. Is it just because of the c++ part of odin? We were talking about options for downcasting, but maybe a proc group could also be an option while not having to store any extra data in the struct? I have no idea, it just sounds odd going back to proc groups after the limitations we were talking about. I wonder what would be cheaper, letting a proc group handle the polymorphism, or using a union subtype polymorphism as discussed

  • Jesse:

    • Nothing to do with the language choice for the compiler.

    • It's a compile time switch basically. A better designed _Generic  macro from C.

    • They act on type information available at compile time. There's nothing runtime about proc groups.

Generics

  • Use of $T  in parameter type  of the procedure.

  • Parametric Polymorphism .

  • Parametric Polymorphism .

  • Fun facts :

    • Parapoly doesn't support default values.

      • []$MEMBER  can't have a default value, for example.

  • Specialization :

    array: $T/[dynamic]$E
    
    • T :

      • Type of the entire array.

    • E :

      • Type of the element inside the array.

Force parameters to be compile-time constants

  • Use of $T  in parameter name  of the procedure.

my_new :: proc($T: typeid) -> ^T {
    return (^T)(alloc(size_of(T), align_of(T)))
}

ptr := my_new(int)

Deferred

  • @(deferred_in=<proc>)

    • will receive the same parameters as the called proc

  • @(deferred_out=<proc>)

    • will receive the result of the called proc.

  • @(deferred_in_out=<proc>)

    • will receive both

  • @(deferred_none=<proc>)

    • will receive no parameters.

Return from a deferred procedure
  • what happens if I have a @(deferred_none=end) begin :: proc() -> bool  and a end :: proc() -> bool , and I call result := begin() ? How does the return of deferred procedures work? Would result  hold the value of begin  or something else?

    • result  will hold the return value from begin , the return value of end  will be silently dropped when it runs

    • It'd be equivalent to

    result := begin()
    defer end()