Lifetimes ========= Normal, no lifetime change: func print(ref T p) -> void Taking ownership: func destroy(own T p) -> void Giving ownership: func create() -> own T Letting parameter borrow: func add(ref T container, ref T content_to_add) -> void lifetime content_to_add >= container Letting returned value borrow (note: no operation on "container" may reduce the lifetime of the returned item after a call like this) func get(ref T container) -> ret T lifetime return >= container Giving ownership with return-related lifetime constraint on parameter: func create(ref T p) -> own T lifetime p >= return Rules for lifetime specifiers: - the left side must NOT be "own" (how about "arena"?) - if the right side is a parameter, then the parameter must be "ref" (XXX check this) - if the right side is "return", then the return must be "own" - a returned value MUST either be "own", or it must be on the left side of a lifetime specifier, with the return on the right side. lifetimes and aliasing ---------------------- An object that references another object, could alias other objects. For this reason, it is meaningful to allow qualifiers on lifetime items: - "lifetime p >= q" p may be read by operations on q. - "lifetime var p >= q" p may be read and modified by (var) operations on q. - "lifetime addr p >= q" p may not be accessed by operations on q. - "lifetime writeonly p >= q" p may only be written to by operations on q. - "lifetime readonly p >= q" p may be read be operations on q (useful when p is "var", for example) The default should be inferred from the qualifier of the parameter. Threaded access could be allowed by specifying "threaded" on the parameter. Various notes ------------- /* FIXME should we have two sets of lifetimes? - lifetimes at start of function call (in) - lifetimes at end of function call (out) (and lifetimes that apply to both start and end of call) */ /* FIXME Also, lifetime can be transferred. - e.g. from a parameter to a data structure - so in/out keywords can appear on both sides of the >= XXX maybe this can be inferred practially at all times? - own parameter = in - own return = out => so "own" without lifetime = unbounded lifetime transfer and "own" with lifetime = bounded lifetime transfer and "ref" with lifetime = bounded lifetime borrow and "ref" without lifetime = implicit (input: borrow during call, return: lifetime == inherit arena lifetime) Do we need == also? - "lifetime in param1 == out obj.param2 What should happen with the "unspecified side" (e.g. "out" when "in" is specified)? - By default, parameters are owned by the caller */ /* FIXME Complicated lifetimes can be constructed with type qualifiers: "ref own" (caller most likely still have the ref somewhere, it has to be prevented from using it) */ /* TODO it would be nice to have "virtual variables" tied to private types that can have lifetimes to (and also to be able to track fields of non-private structs also) - related: pointers-to-pointers and lifetimes */ /* TODO future expansion: lifetimes tied to type states (e.g. gives lifetime only in a certain typestate) */ * How to prevent leaks with cycling own-references? type Thing = { own Thing th } func f(arena) { own Thing th = .allocate(arena) th.th = th # <--- !!! } * Lifetimes of static data? - It would be nice to support this! - This is NOT the same thing as "infinite" lifetime, if the system allows modules to be unloaded (i.e. any system with dynamic loading, e.g. dlopen/dlclose, LoadLibrary/FreeLibrary, etc) # returns an item in the static const area of this module func f() -> static Item # returns an item in the static const area of the module "othermod" # (should it be required to be in \interface_depends, or only \depends ?) func f() -> ref Item lifetime return >= static(othermod) - What should the keyword be? - modown <-- most logical in return values? but could be confused with ownership of the module itself! - modref <-- most logical in parameters? - static <-- familiar for C, C++ and Java programmers? but could also be confusing if used in structs. How to declare that a function unloads a module? - Perhaps a special "module" type? func .load(string name) -> own module func .reload(own module mod) -> own module func own module.unload() * Qualifiers for common cases? (maybe not needed) - Object-owned data: func ref SomeThing.get_details() -> thisown Details - What should the keyword be? - thisown - objown - ref(this) - own(this) - thisref - BUT a plain "ref Thing" return does NOT make any sense without a lifetime. So the default lifetime could be: 1. that of the "this" ref 2. that of the module * Returning a struct (on stack) with references to owned objects? - Would need some special syntax... - perhaps a "stackown" qualifier? * Add some way of safely calling non-SLUL code, without triggering undefined behavior. SLUL assumes the following: - a function can ONLY access it's parameters (and nested objects), and only with the access specified by the type qualifier / reference type. - all I/O, including memory allocation, must be done through an arena, which must be reachable via the called function's parameters (taking qualifiers into account, so at least one parameter must be marked "arena"). - Add a keyword? - system - native <-- this is what Java uses. - extern <-- this is what Rust uses. # Which syntax to use? func do_stuff() -> int system sysfunc do_stuff() -> int syst func do_stuff() -> int system func do_stuff() -> int func system do_stuff() -> int # <-- best choice?