Conventions =========== This document expands on the :ref:`style` to elaborate on some informal conventions used in the Hare community. Ownership and borrowing ----------------------- Hare does not enforce temporal memory safety, but we use the vocabulary of temporal memory safety to reason about memory, especially in the documentation of functions. Using a shared vocabulary makes it easier to reason about resource management. The *lifetime* of an object is the period of time between when it is created (allocated, opened, etc) and when it is released (freed, closed, etc). There are three basic lifetimes for memory objects: - *Static* or *global* lifetimes exist for the entire lifetime of the program, such as global variables. - *Stack* lifetimes are tied to the lifetime of a stack frame, i.e. from the moment a function creates an object on the stack until the moment that function returns. - *Heap* lifetimes refer to allocations on the heap, i.e. from the moment of ``alloc()`` up until the moment of ``free()``. .. note:: This vocabulary can be generalized beyond memory allocation patterns. For instance, a file descriptor may be *owned*, and its *owner* would be responsible for closing it (with ``io::close``). The lifetime of a resource can be related to the lifetime of another resource. For example, a parser may store its state in a memory object with its own lifetime. The parser state may be said to *own* the file handle it's reading from, as well as perhaps a heap-allocated buffer for buffered I/O reads. The lifetimes of these resources are related to the lifetime of the parser. The owner of a resource can also *lend* it to code that *borrows* it from the owner. A resource is considered *borrowed* if the agent which is using it is *not* responsible for its lifetime. A resource which remains borrowed after the end of its lifetime is likely to lead to a *use-after-free* error. Ownership can be *transferred* as well. An object's ownership can be *assumed* by an agent. For example, if the owner of the parser opens the file the parser will read, but the parser will close the file when it's done parsing it, then ownership over the file descriptor should be *transferred* to the parser, and the parser *assumes ownership* of it. Note that a reference has a lifetime as well. For owned objects, the lifetime of the reference is equal to the lifetime of the object. For borrowed objects, the lifetime of the reference is equal to the duration the object is borrowed. .. code:: hare fn owner() void = { // "counter" is owned by "owner" and has a stack lifetime let counter = 0; // "owner" lends "counter" to borrower borrower(&counter); // "counter" is automatically released when "owner" returns from its // stack frame. return; }; fn borrower(i: *int) void = { // "borrower" borrows "i" from its caller. // // i is a reference to an int (*int). The lifetime of the reference is // equal to the lifetime of "borrower"'s stack frame. *i += 1; }; Recommendations for function names ---------------------------------- .. note:: See :ref:`cnames` for information on naming Hare symbols after foreign symbols like those defined by system libraries. To name a function, first identify its purpose. If you were to describe this purpose in a sentence, you should be able to identify up to three grammatical items of importance: the verb, the object, and the subject. The subject is usually the actor, the object is usually being acted upon, and the verb defines the action being taken. When naming a Hare function, use the format “subject_verbobject”, where the subject preceeds the verb and object, separated by an underscore, and the object directly follows the verb. If you can infer the subject or object from context, they may be omitted, so “verbobject”, “subject_verb”, or simply “verb” may be appropriate names. In the sentence “Sam goes to the store”, “Sam” is the subject, “store” is the object, and “go” is the verb. The equivalent function name would be “sam_gostore”. If we have additional context, for example if this is in the “sam” module, we could call it “sam::gostore”. Or perhaps the object is given by a parameter, in which case “go” is sufficient: .. code:: hare fn sam::go(to: destination) void; Abbreviating terms is acceptable, such as “str” for “string” or “tok” for “token”. This approach prefers terseness when unambiguous. Here are some real-world examples: .. code:: hare fn bufio::scantok(stream: *io::stream, delim: u8) ([]u8 | io::EOF | io::error); fn io::read(stream: *io::stream, buf: []u8) (size | io::EOF | io::error); fn lex::init(in: *io::stream, path: str) lexer; Note the arrangement of parameters: the object being acted upon comes first. For example, if you have “source” and “destination” parameters, “destination” should be placed first in the parameter list. Verbs for allocation strategies ------------------------------- It is useful to communicate the allocation strategy in function names, to lend readability to the implications for Hare’s manual memory management system. The following conventions are recommended. For functions which initialize a value and return it, either via allocation or via the stack, name these functions after the object being initialized. For example, to initialize a SHA-256 hash, you use ``crypto::sha256::sha256()``. If a more specific verb than “allocate” or “initialize” would be appropriate, name the function after that verb. For example, “open” or “connect” may be more appropriate names than “file” or “client”. If a function accepts a pointer to a value as a parameter, and will initialize that value, use the “init” verb to name the function. For functions which free resources associated with an object, if the object itself is freed, use the verb “free”. If the object itself is not freed, but some other state associated with it, use the verb “finish”. Recommendations for documentation --------------------------------- Consult ``man haredoc`` for technical details regarding documenting Hare interfaces through comments in the source code. It is useful to have some linguistic conventions for inline documentation. The following guidelines provide such conventions for any programs which wish to be consistent with the rest of the Hare ecosystem in their approach to API documentation. All programmer-facing documentation should be written in English, and all public (exported) members should be documented. Not documenting an exported member signals that it is not designed to be used by third-party programs. Your documentation should be as concise or as long as is necessary. Programmers reading the reference documentation are usually in a hurry, but they also appreciate comprehensive explanations. Aim to be as short as possible without omitting necessary details. Include a period after the last sentence. Function documentation should complete the following thought: “This function [does, will, is used to]…”. Examples: - “Insert a new entry into the list” - “Parse the next record from the file” Type documentation should complete the following thought: “This type is…” or “This type $verbs…”. Examples: - “An error indicating that an invalid sequence was encountered” - “Indicates that more data is required to finish processing” - “Stores the state for an XML parser” Constant documentation should complete the following thought: “This constant is…” - “The size, in bytes, of an MD5 digest” - “The magic string identifying a PNG file” Recommendations for errors -------------------------- These recommendations cover programmer-facing errors. User-facing errors have different concerns, for example they may require internationalization. Each module should provide an ``error`` type which is a tagged union of all possible errors which might be returned by functions in that module, and an ``strerror`` function which explains the error as a string. These strings should be written with “Sentence case” and should not end with a period. It should also be written so that it makes sense when passed to ``fmt::fatal("Error:", example::strerror(err))``. Write programmer-facing error messages in English. This includes the return value from ``strerror``, and also the error messages used in ``abort`` and ``assert`` expressions. .. _cnames: Conventions for foreign symbol names ------------------------------------ When writing Hare symbols whose names are more authoritatively specified elsewhere, different conventions apply, such as in the following scenarios: - Forward-declaring symbols defined by a system library, e.g. ``SDL_Init`` - Naming symbols after well-known constants, e.g. x86_64 register names - Naming symbols that reference names defined by a specification, e.g. ``EFI_FILE_PROTOCOL`` In this case the convention is to use the naming scheme defined by the more authoritative source, ignoring Hare's normal naming and case conventions for those symbols. For example, ``SDL_Init`` should not be renamed to the more idiomatic Hare convention of ``sdl_init``. However, you MAY make the following changes: - Removing a namespace suffix which is more adequately represented by a Hare namespace, e.g. ``SDL_Init`` becomes ``sdl::Init`` (note "I" remains capitalized). - Removing Hungarian notation prefixes, particularly for C enums, e.g. ``SDL_BLENDFACTOR_ZERO`` is a member of enum ``SDL_BlendFactor`` and can simply be called ``ZERO`` if placed into a Hare type called ``sdl::BlendFactor``.