.. _initequals: .. default-domain:: chpl ================== The 'init=' Method ================== Overview -------- Historically the Chapel language has not supported a way for users to directly handle initialization from an arbitrary expression. Typically the compiler would first default-initialize the variable, and then assign the expression into that variable. For example, if a user wanted to initialize their own list type from an array literal they would need to implement the assignment operator: .. code-block:: chapel record IntList { var D : domain(1); var A : [D] int; } operator =(ref lhs : IntList, rhs : []) { lhs.D = rhs.domain; lhs.A = rhs; } var i : IntList = [1, 2, 3, 4, 5]; // becomes... // i.init(); // i = [1, 2, 3, 4, 5]; Though this process can work in some situations, there are some downsides to consider: - Error messages confusingly mention assignment instead of initialization - Fields used in this process could not be 'const' (assignment requires mutability) - This is not true initialization, and may have unnecessary overhead In order to rectify these issues, a new method named "init=" has been created to replace the "init" method for copy initialization and for initialization from arbitrary expressions. Why a New Method is Necessary ----------------------------- Since their introduction, initializers have served as the only mechanism for initializing types. Many existing initializers have been written for use with new-expressions. If those initializers were invoked when initializing a variable from an arbitrary expression, then the resulting initialization patterns may be surprising and unintended. For example, consider a list with an initializer that accepts an integer representing the list's length: .. code-block:: chapel proc IntList.init(len : int) { ... } var x = new IntList(5); // creates a new IntList of length '5': x.init(5) If this initializer were invoked for initialization from arbitrary expressions, then users could initialize this list from an integer. This is unlikely to be what the author of the type intended: .. code-block:: chapel var y : IntList = 10; // becomes: y.init(10) By creating a new method we can separate the two use-cases and allow for further control by the type's author. General Rules for 'init=' ------------------------- The ``init=`` method is in many ways similar to the ``init`` method, and they share the same semantic rules within the body of the method. For example, fields must be initialized in declaration order, and must be initialized unless they have a default value. Where ``init=`` differs is in its invocation. The ``init=`` method will only be invoked in two cases: copy initialization and initializing a variable from an expression. The ``init=`` method will only be invoked with a single value. The ``init=`` method may also invoke other initializers through ``this.init(...)``, but currently may not invoke other ``init=`` methods. Classes do not support the ``init=`` method because classes will not be copy-initialized by the compiler. The 'init=' Method for Non-Generic Types ----------------------------------------- The compiler-generated ``init=`` method for non-generic types is simple. It accepts one argument of the same type: .. code-block:: chapel record R { var x : int; } // identical to compiler-generated implementation proc R.init=(other : R) { this.x = other.x; } In order to override this compiler-generated implementation, the user must implement an ``init=`` method that can accept an argument of the same type. An ``init=`` method may also specify a type other than the one being initialized. Such user-defined initializers will disable generation of the default implementation of a type's ``init=`` method, and users must provide an equivalent implementation. When initializing from a different type, users must also provide a cast implementation between their record and the other type. This requirement exists for consistency and completeness, as both ``init=`` and a cast create one type from another. These rules can be observed in the following example, which implements a record that can be initialized from an ``int``. .. code-block:: chapel record R { var x : int; } // Required due to the user-defined 'R.init=(int)' proc R.init=(other: R) { this.x = other.x; } proc R.init=(other : int) { this.x = other; } // Cast required due to the user-defined 'R.init=(R)' operator :(val: int, type T : R) { return new R(val); } var A = new R(10); // compiler-generated initializer var B = A; // B.init=(A) , user-defined ``init=`` var C : R = 10; // C.init=(10) , user-defined ``init=`` // var D : R = "hello"; // D.init=("hello") , unresolved call! The 'init=' Method for Generic Types ------------------------------------ The compiler-generated 'init=' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The compiler-generated ``init=`` method for generic types requires knowing the intended instantiation in order to disallow copy-initialization from different types. In the following example, there should be a compile-time error when attempting to initialize a ``R(int)`` from a ``R(real)``. .. code-block:: chapel record R { type T; var x : T; } var x : R(real); var y : R(int) = x; This is accomplished by allowing ``init=`` to query the intended instantiation through the expression ``this.type``. The compiler-generated ``init=`` for type ``R`` looks like: .. code-block:: chapel proc R.init=(other : this.type) { this.T = other.T; this.x = other.x; } The first line of this ``init=`` may seem unnecessary, since ``this.type`` must already be known. The line ``this.T = other.T`` is currently used by the compiler to ensure that the types match. If the user attempts to initialize ``this.T`` with a type different from ``this.type.T`` the compiler will issue an error. Future releases may allow this field initialization and type check to be omitted, and instead infer the type from ``this.type.T``. Field-Based Constraints ^^^^^^^^^^^^^^^^^^^^^^^ The ``this.type`` query can also be used to constrain the given value based on generic fields. For example, consider the following generic record that simply wraps any given type: .. code-block:: chapel record Wrapper { type T; var x : T; } A simple ``init=`` for this type may try to infer ``T`` from the given value: .. code-block:: chapel proc Wrapper.init=(value : ?T) { this.T = T; this.x = value; } This only works as long as the desired instantiation of ``T`` and the type of the value match. What if a user tried to initialize a ``Wrapper(int(8))`` from an integer literal? .. code-block:: chapel var x : Wrapper(int(8)) = 5; The type of ``5`` is actually ``int(64)``, and the ``init=`` would fail at the line ``this.T = T;``. Furthermore, because ``value`` is a fully-generic argument this ``init=`` would also resolve as the copy initializer, and attempt to initialize some sort of nested ``Wrapper(Wrapper(int(8)))`` type. A better approach is to constrain ``value`` using ``this.type``: .. code-block:: chapel proc Wrapper.init=(value : this.type.T) { this.T = value.type; this.x = value; } var x : Wrapper(int(8)) = 5; // x.init=(5) var y = x; // compiler-generated init= The literal ``5`` will now coerce from ``int(64)`` to ``int(8)`` following regular Chapel semantics, and the compiler-generated ``init=`` will be invoked when initializing variable ``y``. Using 'this.type' Inside 'init=' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A type may be initialized from a value that represents only part (or none) of the required instantiation information. For example, consider initializing a distributed list type from an array: .. code-block:: chapel record DistList { type DistType; type eltType; // ... } proc DistList.init=(arr : [] ?eltType) { this.DistType = this.type.DistType; // from variable declaration this.eltType = eltType; // from 'arr' // ... initialize data, etc. ... } // Initializing a Block-distributed list from an array literal var x : DistList(Block(1), int) = [1, 2, 3, 4, 5]; In this example snippet, ``this.type`` is used within the ``init=`` body in order to achieve the desired instantiation. Part of what was needed was available from the given value (i.e. the element type), but the rest was taken from ``this.type``. Note that only fully instantiated types can be initialized in this manner. Future releases may add support for fully or partially generic ``this.type`` expressions. Initializing with a Generic Expression ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the variable declaration's type expression is fully generic, then the value expression must be a subtype of of that generic type expression. In such cases the compiler infers the type of the variable to be the same as the value's type: .. code-block:: chapel record R { type T; var x : T; } var A = new R(int, 5); var B : R = A; // 'B' inferred to be of type 'R(int)' If the value is not a subtype of the generic expression, then there will be a compile-time error. This may change in future releases. Disabling Copyability --------------------- If a user wishes to indicate that their record cannot be copied, they can do so by implementing an ``init=`` method with a ``false`` where-clause: .. code-block:: chapel proc R.init=(other: R) where false { // method body may be empty in this case } A call to the ``compilerError`` utility function can be used for the same purpose: .. code-block:: chapel proc R.init=(other: R) { compilerError("Cannot copy R"); } Relation to Assignment Operator ------------------------------- In the 1.20 release users could choose to implement either the ``init=`` method or ``=`` operator for a given type, or implement both, or rely entirely on the compiler-generated implementation. This could lead to hard-to-debug problems when both functions appeared to be user-defined, but a user mistake in the function signature caused it to be ignored and the compiler-generated version to be used instead. In the 1.21 release users are now required to implement both the ``init=`` method and ``=`` operator for a given type, or rely entirely on the compiler-generated implementations. If only one implementation is found, the compiler will issue an error and any potentially-incorrect function signatures will hopefully be exposed.