Pointer Types

Assignment vs. passing to function...

I think rather than having a bunch of sigils (*, &, @, $) for pointer types, I've realized that really the difference is in move/copy semantics:

  • Unique pointers:
    • Copy (&): Invalid
    • Move (*): Transfers ownership
  • Shared pointers:
    • Copy (&): Increments resource count
    • Move (*): Decrements resource count (then increments it, all atomically)
  • Weak pointers:
    • Copy (&): Gets the shared pointer if valid, and increments resource count
    • Move (*): Invalid
  • Values:
    • Copy (&): memcpy
    • Move (*): Invalid

Struct Fields

How to specify either a pointer or the whole thing, i.e.:

typedef struct {
  const char *name;
  unsigned char age;
  const char *species;
} Pet;

typedef struct {
  const char *name;
  unsigned char age;
  Pet *pet;
  Pet whole_pet;
} Child;

And then & can stick around as the reference-to operator.

Probably exactly how C does it; using *.

To Do

Define a range inside a function.

Remove struct, it's type, component, :fields and :components or bust now.

Can you iterate over a container of unique pointers? No. What if you super need to? Do you move them all and then move them back? Seriously?

Need to add private struct fields.

It's pretty inconvenient to build a completely new discriminated type whenever the discriminated field changes.

Returning value types is a thing. In C we do this with output parameters, and for safety the output parameter is only written to on success.

Varargs (...)

There are three string types, in actuality:

  • dynstring: string buffer
  • string: char*
  • str: const char[]

Empty return should not require parentheses

Defining a type based on a primitive (like dynarray or string) should work like:

string ErrorMessage("Error!!!!")
dynarray Games(type: string)

These aren't bindings (they don't start with var, and they only work in file scope, so it's probably not that tricky. The partial params (in the Games example) are maybe a little tricky but whatever.

Ehhh I don't know how useful this is. I think probably it's only useful to parameterize the types. Probably requires a little more clever compiler magic to check what type arguments a constructor takes and see that only those are specified, but that's fine.

To Decide

  • Probably get rid of special type identifiers altogether (). They're identifiers just like anything else because they're first-class, so they can be looked up just like anything else.

  • Rethink exceptions

  • Should add replacements in array and dynarray for memcpy memset

  • There's some weird overlap between property control (only define get for a property) and mutability.

  • Docstrings

    • Research popular documentation generators (Sphinx, Doxygen, etc.?)
  • Serialization

    • Cyclical references are the main problem here.
    • I think this is probably library functionality, but we can help it with introspection
  • Tools

    • Statically linked binaries
    • Formatter
    • How to do testing?
    • Some kind of devel mode so that:
      • Dead code is allowed
      • Dead imports are allowed
    • Is it possible to continuously compile a project, and warn about (the many) potential problems during development? This would save a huge amount of frustration.
    • Fetching packages
  • type (struct) packing

    • define a separate :pack field?

Probably Not

  • Parametric polymorphism

Allocation

Allocating a big chunk of data and then assigning fields to that data, instead of allocating several chunks of data.

  • I think texels are the example? Allocate 20 texels (f32 x, y, z, GLuint texID) in 4 rows instead of 20 columns.
  • This is something like "describing" the data right. Instead of creating a description then asking the OS to allocate that, you ask the OS to allocate a big slab and then describe or annotate the fields.

I'm feeling a separate kind of function here, like alloc or something. Needs more consideration.

To Write About

  • No type inference in struct fields

  • You can set static types/structs as type/struct fields

    • This also goes for containers
  • Generics

    • Use @ instead of that shitty syntax
    • Use gtype SuperTable<@key_type, @value_type>
    • Use gtrait Hashable(@type thing)
    • Use gfn add(@type a, @type b)
  • Compiler directives:

    • Overflow (@overflow)
    • Type sections (@variants)
    • Generic type placeholders (type SuperTable<@K key, @V value>)
    • Global namespace (@globals)
  • const

    • keyword for variables
    • Single line const blocks for single constants
  • Operator overloading

    • @op:iter, @op:getitem, @op:setitem (borrow heavily from Python here)
    • I feel like these should also be traits
    • Context manager (@op:enter and @op:exit)
  • It doesn't seem possible to implement fields in Traits any more efficiently than just calling a function if you're starting from an unknown type.

    • From an LLVM point-of-view
  • Scopes

    • Explicit @global scope; solves weird variable scope and shadowing problems too
      • @global is like a big struct; all its fields have to be declared ahead of time. It's not a magic JavaScript/Python blob of properties.
    • Constants
      • Const structs can't have heap members, nor can their members have heap members, etc. etc.
  • Introspection:

    • Introspection is a compile-time thing
    • Access a type/struct/whatever with Person::
      • Person:fields
      • Person:name
    • At runtime, you can access an instance's type:
      • charlie:type:fields
      • charlie:type:name
    • You can also access the current block at runtime:
      • :this:type
      • :this:arguments (for functions)
      • :this:name and :this:number for const blocks (for example)
    • You can access super:
      • :super:type:fields
    • what stuff is available in what blocks
      • We're essentially talking about the AST right?
  • Variants

    • validation on assignment
    • define a whole new set of fields, even if they're redundant
      • Solves memory layout problem
      • Not super happy with the syntax here
    • Size of a variant type is always the max; like a tagged union
      • But the implementation is such that each variant has its own struct type... internally
      • And the size of an instantiated variant type is the size of its variant variant
    • variants can change if all fields are updated
  • FFI

  • Concurrency/Parallelism

    • Basically what Rust does (Send/Sync)
  • Modules