-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User-defined implicit initialization hooks #252
Comments
This is not an addition to RFC - just some ideas that might potentially be useful. It is not uncommon to see procedure implementation pattern where If ref variable really has to be More on 'broken type system' - object that have non-trivial initial state (e.g. not just zero-filled memory) are more fragile in cases where implicit initialization is not configurable - you must take care and use dedicated constructors all the time, even in situations like Another (mostly theoretical) idea is that it might be possible to automatically add finalizers for ref objects if they are created using proc `=init`(v: ref var T) = new(v, final) |
How would exceptions be handled? |
If you mean exceptions in the Although I'm not sure if I understand what exact scenario you have in mind - if you could elaborate on your question I might provide better answer if possible. |
Just like we require
|
Pretty sure it's supported and partially constructed objects are deconstructed properly. Looks super expensive to implement (like everything else in C++ I guess). |
-1 from me. First of all construction is very different from destruction, constructors take parameters in most languages and the problem is worse when "size hints" optimizations enter the picture: A size hint should be attached to an object, not to an object's type. Furthermore the mechanism will soon be misused to avoid the The route forward IMHO is to allow default values inside object declarations with the restriction that the value has to be a compile-time value. For multiple reasons:
|
Main point is - with constexpr as default values there is no way to execute code when implicit initialization happens. Yes, in overwhelming majority of use cases constexpr is more than enough, but this route completely closes way for non-trivial logic in implicit initialization which might be necessary in some cases. It is possible to place additional restrictions on
I would argue that
Again - since there is no support for parameters in
Again - this is not about explicit constructors - we already have them (
I'm sorry, but I don't follow how this would prevent it. If you mean type
Obj = object
id: int
proc initObj(): Obj = Obj(id: generateUniqueId())
proc `=init`(obj: var Obj) = obj = initObj() Allows include If this is a 'misuse' you were talking about - I think it is necessary to have some way to configure this behavior and cut chain of "if A includes B I must initialize A it correctly using This is basically the same as This problem is quite nicely illustrated by |
Ok, this wasn't clear to me before, thanks! But then your proposal is mostly a different syntax for
I think that's a problem that can be solved by special casing |
Yes, exactly. I think that arbitrary expressions might be necessary in some cases, but I agree that it is not possible to make things less strict so starting with constexpr and potentially expanding into
|
So can we agree on supporting it in this way: type
StartWith1 = object
x: int = 1 ? |
Yes. It covers main concerns about type guarantees invalidation (which is really important) and other complex cases of default initialization would be nice to support, but not right now at least. |
@Araq it's not entirely clear what proposal led to "Accepted RFC", is it the following: let a3 = 3
type
Foo = object
x1: int = 1 # ok
x2 = 2 # ok, type inference allowed in initializer
x3: int = a3 # CT error, field initializer must be const ? note 1:it would currently prevent initializers that are ref/ptr/pointer: type Bar = ref object
b0: int
type Foo = object
b: Bar(b0: 1) # error: initializer is a ref and can't be const EDIT: this restriction could be lifted by allowing const ref objects, by accepting nim-lang/Nim#15528 note 2:this caveat applies: type Foo = object
x1: cstring = "abc"
var a = Foo()
a.x1[0] = 'A' # SIGBUG [EDIT] note 3see #126 (comment) for a more detailed proposal that also covers: `var a: T` # always equivalent to `var a = default(T)`
# `default(T)` is defined recursively in the obvious way, taking into account default intializers for object types, eg: see example provided there |
Yes.
well |
The problem I see with @Araq syntax type
StartWith1 = object
x: int = 1 is that it works only for object initialization. You can't use it with other types like type
Ranged = range[10 .. 20] # I would like to have 10 as default
BoolTrueDefault = bool # This type of bool should default to 'true'
Constraint[T] =
c: T # When implementation is delegated to another client module,
# default initialization should be too. |
First two types here are not Default initialization of distinct types is also an important case to consider, but I just can't see how this can be added in type definition syntax. In objects value for
|
|
In my opinion it makes sense if the goal is to prevent invalid states. Destruction turns a value from a valid state to an invalid state, initialization turns it from an invalid state to a valid state. Optimizations like |
Would it also be possible for this RFC to support tuples? I didn't see an example using them yet: type
StartWith1 = tuple
x: int = 1
StartWith2 = tuple[y: string = "2"] |
Tuples are different than |
User-defined implicit initialization
This RFC mostly reiterates ideas from #48, #126, #233
Add support for user-defined implicit initialization hook with following prototype:
Is this needed?
Existing proposals
There has been several RFCs related to default initialization/implicit construction for user-defined types.
Existing compiler warnings
Nim compiler already provides two warnings directly related to default initialization, three more related to initialization in general, making total of five initalization-related diagnostics, meaning there is at least some interest in correct initialization behavior
UnsafeSetLen
- "setLen can potentially expand the sequence, but the element type '$1' doesn't have a valid default value"UnsafeDefault
- "The '$1' type doesn't have a valid default value"ProveInit
"Cannot prove that '$1' is initialized. This will become a compile time error in the future.",ProveField
"cannot prove that field '$1' is accessible",ProveIndex
"cannot prove index '$1' is valid",{.requiresinit.}
Separate pragma
{.requiresinit.}
to completely prevent implicit default initialization. Used really infrequently (only 126 times in 1340 packages - approximately 90% of packages I checked haven't used it even once)It is not possible to contain effects of
requiresinit
- once added it affects all code that uses type with annotated fields. It also affects templates that rely ontype Res = typeof((var it {.inject.}; op))
to determine type of expression (right now almost none of the*It
templates can deal with these types).Why this is needed?
Broken type system
As mentioned in these comments by @timotheecour large portion of type safety guarantees is invalidated - enum with offset, ranges now can't really guarantee anything unless explicitly created with
initT
. Any kind of value that has non-zero default requires special attention - it is now your responsibility to make sure this-1
-as-default-value is actually used.{.requiresinit.}
is a solution, but has already mentioned it propagates through whole codebase, requiring far-reaching modifications.NOTE: I personally think that
{.requiresinit.}
is a great way to explicitly declare requirements and enforce them via compiler diagnostics. The only drawback is that it is really viral and has to be worked around in some cases (typeof
pattern can just be written asvar tmp: ref InType; var it {.inject.} = tmp[]; op
).`=destroy`
confusionIt is possible to have specific destruction hook, bound to particular type and you can write
initT
proc for user-defined constructor, but when it comes to default initialization everything is just filled with zero and that's it. It is also possible to completely forbid implicit initialization, but not configure it. I find it rather confusing and counter-intuitive.Large number popular imperative/OOP programming languages provide way to customize default values. Out of all languages mentioned in
nim for X programmers
on wiki onlyC
lacks this feature.constructor
keywordDefault::default()
Other concerns
RFC #126 (Support default values for object properties) suggests implementing default value initialization in form of
Which can be implemented using macro (see forum thread) and it is not necessary to add this into language core. If one wishes they can use macro to automatically declare `=init` hook. It is already possible to do for explicit initialization
initT
procs, but default initialization is not currently configurable.Possible implementation behavior
Similar to how
`=destroy`
is handledIf type does not have user-defined
`=init`
then no injection shall happen. If any of the fields have initialization declared then default initialization in form ofis implicitly declared recursively. If field is has type range or enum for which
low(Enum).int != 0
orlow(range[..]) != 0
then`=init`
is implicitly declared too.Object construction syntax. If field is not initialized by user explicitly and field type has
`=init`
declared field should be implicitly initialized. If forced explicit initialization is necessary then{.requiresinit.}
can be used on object field.NOTE:
{.requiresinit.}
already uses similar logic - if type field cannot be default-initalized then none of the object containing file of this type can be default-initialized too.The text was updated successfully, but these errors were encountered: