-
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
Support default values for object properties #126
Comments
Relevant: http://forum.nim-lang.org/t/703 EDIT: URL updated |
This still need to be a feature 3 years later |
@dom96 The reason @FedericoCeratto's link does not work is because it ends with a forward slash. If I'm not mistaken, you mentioned this issue in IRC, correct? |
I'm having a hard time understand how something as fundamental as this is considered |
It is by design that Nim does not need constructors. Objects are by default initialized with 0 memory and it is the programmers job to create the type in a way so that the 0 memory isn't an illegal state of the object. Since seq and string are now by default empty and not nil anymore, the biggest reason to have inititializers/constructors is now gone. Currently I don't see any good reason to add destructors that justifies the costs of having destructors. Nim isn't the only language that works this way, for example Go works exactly the same way. |
what do you mean by |
I am not talking about runtime costs. I am talking about costs to implement it, fixing the bugs and conflics that arise from it. Live with the fact that constructors are a thing and value types are no longer all zero anymore. Did you ever watch a lesson about generic exception safe initialization in c++? It's nuts and not worth it. memset to 0 can't raise an exception. |
the alternative is to make initialization explicit, eg: IIUC that would alleviate much of the complexity issues you're referring to. |
This isn't fundamental. There is plenty of languages that don't allow this. Plus this is yet another feature that is easily doable within the confines of existing language features: just use a constructor procedure. |
@dom96
@krux02
not relevant. We're talking (or at least in my version of this RFC) about default values known at CT, whereas C++ has to deal with RT initialization. Implementation is not complex (in fact we already have nim-lang/Nim#12378). without this feature, the type system is brokenThere's another important use case not mentioned here: type safety guarantees. block:
type R = range[10..13]
doAssert R.default == 0 # broken
var a: R
doAssert a == 0 # broken
type Foo = enum k1 = 10, k2 # not even enum with holes
var b: Foo
doAssert b.ord == 0 # broken
doAssert $b == "0 (invalid data!)" # broken
## affects any type that has a (sub-) member with non-valid-default
type Bar = object
a1: Foo
a2: R
# var c: Bar # this doesn't even compile
var c = default(Bar) # that does compile, but still broken
doAssert $c == "(a1: 0 (invalid data!), a2: 0)" note that some of these generate a warning ( real life examplecomplications arising from DateTime, which contains both range[1..31] and enum with offset (see #211) |
Well yeah, but there are two conflicting designs here: "Just make the default value sane" vs "Just make initialization explicit" and only the latter can fix our "not nil" issues. |
this opens a can of worm similar to C++ static initialization order fiasco, eg http://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order.html it also doesn't play well with compile time code and generic code
That being said, I think we can also make it work for not nil types. Here's my proposal
type
Normal = ref object
TBar = object
b1 = "abc"
b2: range[10..12]
Bar = ref TBar not nil
Foo = object
x1: Bar
x2: Bar
x3: seq[Bar]
x4: Normal
static:
assert default(Normal) == nil
const b0 = default(Bar)
assert b0 != nil
assert b0[] == TBar(b1: "abc", b2: 10)
assert default(Foo) == Foo(x1: b0, x2: b0, x3: @[], x4: nil)
var a: Foo
let b = default(Bar)
assert b != nil
# b is a (singleton-like) instance intialized in module constructors
# no C++ static initialization order fiasco in our case since the default values
# are known at CT and there are no cycles
assert b[] == TBar(b1: "abc", b2: 10)
assert a == Foo(x1: b, x2: b, x3: @[], x4: nil)
## note: b0 and b's addressed are unrelated, eg:
const b0Adrr = cast[int](b0)
assert b0Adrr == cast[int](b) # would fail this is all possible by re-opening nim-lang/Nim#13082 The only thing needed in that PR is either disallowing const b0 = default(Bar)
#var a = b0 # either CT error or map it to a statically initialized variable
var b2 = b0.b2 # ok
assert b2 == 10
assert b0.b1 == "abc" |
This is a totally unproven design though, complex to implement, much "magic" going on, unknown pitfalls are awaiting us. All the recent developments in other programming languages focus on "not nil" via some form of explicit constructors/construction. And it works, it's a painful transition for Nim, but worth it. |
this is pretty close to how it works in D; I encourage you to read this short article for more details: https://p0nce.github.io/d-idioms/#Precomputed-tables-at-compile-time-through-CTFE ; it explains the difference between D's enum vs static const
try it out: here's more or less my example i gave in proposal above ported to D rdmd -of/tmp/z01 -L-no_pie t10584b.d import std.stdio;
class A {
int ma1 = 10;
int[] ma2 = [11,12];
string ma3 = "abc";
this(int ma1){this.ma1=ma1;}
}
class B {
A a1 = new A(11); // A could be a "not nil" ref object type in nim's case
A a2;
bool isCT;
this(){
this.a2 = this.a1;
this.isCT = __ctfe;
}
}
void main(string[]args){
auto bRT = new B(); // alloc'd at RT
static const bCT = new B(); // alloc'd at CT
assert(bCT.isCT); // checks it was alloc'd at CT
assert(bCT.a1 == bCT.a2); // checks this is using reference semantics
enum ma3 = bCT.a2.ma3; // checks we can access in CTFE
assert(ma3 == "abc");
writeln(&bRT); // 7FFEEFBFB188 => heap
writeln(&bCT); // 10006F478 => static data segment (at a fixed address if compiled with `-L-Wl,-no_pie` to disable ASLR)
enum bCT2 = &bCT; // checks that address is available at CT
} notethis would make it would also provide RT guarantees to prevent writes to that object, eg could cause a SIGBUG error if attempting to write to ROM. var a = "foo".cstring
a[0] = 'x' nim gives no warning / CT error but at runtime crashes with: wheres in D you have to explicitly circumvent type system to get that SIGBUS: This is yet another use case for introducing In particular for multithreading, where immutable values could be safely shared between threads. |
Yeah well, not every single bug is the language's domain and deterministic crashes are pretty tame to begin with. And enough people gave up on D's way of doing immutability (while still using D). |
I thought about this more in the context of NRVO (eg nim-lang/Nim#14126 and this old forum question does using IMO forcing explicit constructors would not only complicate user code, but would also prevent NRVO in many cases and would likely be more expensive than simply inserting nil checks (combining static analysis + runtime checks when static analysis can't tell).
Note that current nim implementation doesn't always do NVRVO (eg if an object contains an array, see timotheecour/Nim#137 which seems like a bug), but could. And then there's this classic example: https://github.com/nim-lang/Nim/pull/13808/files/4f6cdd797cc0db0410644e7f1216e5264968deeb#diff-f48932f809aa3e1ed2576a4fdd754e26 (seq initialization with not-nil elements) which I really don't see how you can overcome with explicit constructors/construction without a serious performance cost because of things like nim-lang/Nim#13448. My suggestion for the short term is this:
|
Optional range import options
type
Foo = object
bar: range[1001..2000]
proc myproc(a: int, b: Option[Foo] = none(Foo)) =
discard
# or just
proc myproc(a: Option[Positive] = none(Positive)) =
discard The compiler warns me : Edit : No more warning since I upgraded to Nim 1.2.0 . I was using 1.0.6 |
should we close as duplicate of #252 which was marked as accepted in the form written here: #252 (comment) ? (although #126 (comment) has more details) |
Is there some switch to enable defaults in 1.6.10? I tried defaults but it failed |
It's only for 2.0. Sorry, backports are for bugfixes. |
Example:
I can think at least the following benefits:
newMyObj
orinitMyObj
procs)The text was updated successfully, but these errors were encountered: