-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Add get(Option[T], var T): bool
procedure
#23261
base: devel
Are you sure you want to change the base?
Conversation
lib/pure/options.nim
Outdated
@@ -215,6 +215,21 @@ proc get*[T](self: Option[T], otherwise: T): T {.inline.} = | |||
else: | |||
otherwise | |||
|
|||
proc get*[T](self: Option[T], val: var T): bool {.inline.} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But shouldn't that be val: out T
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, but what difference does that do? Is it just a naming standard or does it actually do something? Sorry, I didn't even know that prefix existed before now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it? Wouldn't that require val
to be set before returning? In this case you'd need a val = default(T)
in the isNone
case which doesn't really make much sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As opposed to what though? Otherwise you need the same val = default(T)
on the client side which is worse. Keep in mind that --experimental:strictDefs
is coming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire gist of this function is:
- you have an empty value, say an
int
- you have an
Option[int]
, you don't know whether it contains something or not - you can call this function, if the
Option[int]
wasn't empty,true
will be returned, and you can throw that into an if statement - else, you now know that nothing was done to the empty value and also know that the
Option[int]
was empty
an out
parameter doesn't make sense here if we're returning a bool
, which we'll use for a conditional check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But your
unpackInto
models anout
parameter moreso than it models avar
parameter.
Not really. The concept is that it changes the output to the value of the option only when it is set and returns whether or not it was set. As ElegantBeef showed this can be used to provide an override function where available options will override the currently assigned value or otherwise leave it alone. This isn't just the current get
without a return value, this is an unpacking of the someness of the option and the value.
Not really related to this but you can also use it for a fun option walrus:
template `?=`[T](x: untyped, y: Option[T]): bool =
var x: T
unpack(y, x)
var x = some(100)
if y ?= x:
echo y # This variable is only defined in the scope where the option is valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sequence var x: T; unpack(y, x)
does not compile under --experimental:strictDefs
when unpack
does not take an out
parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, --experimental:strictDefs
is deeply flawed. You can't just make 90% of code invalid in the name of "strictness" or "safety". x
won't even be accessible outside the scope, and since the boolean check returns false in the case of an empty option, there's not gonna be any real problems, unless you're doing some black magic.
I do not want to sound rude or undermining, but I'd really revise strictDefs
before imposing it on everything given how much flexible syntax it renders completely impossible. That was really the magic of Nim that attracted me to learn it and taking it away is a huge slap in the face for a lot of people. This seems like a great idea, but the execution needs to be revised.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strictDefs means that where you did var x: T
you sometimes need to write var x = default(T)
instead. Still seems pretty reasonable to me given that it found real bugs in my own real code and I'm not that bad at Nim programming.
For your get
though it outlines some sloppy design which is why you dislike it. ;-)
I'm sorry if I'm being pushy, but is there anything else needed to be done in this PR? |
We need to agree on whether it's |
I'm very much in favour of picking the correct choice of proc tryGet[T](opt: Option[T], value: out T) =
## Takes an option `opt` and sets `value` to the value stored in the option, if no option is
## stored an exception is throw.
value = opt.get() # Throws an exception if it doesn't have a value
proc unpack[T](opt: Option[T], value: var T): bool =
## Takes on option `opt` and sets `value` to the value stored in the option and returns true.
## If no value is stored in the option `value` is left untouched and this returns false.
if opt.isSome:
value = opt.unsafeGet()
true
else:
false |
The sequence var x: T; unpack(y, x) does not compile under --experimental:strictDefs when unpack does not take an out parameter. Neither does the following snippet: var x: T
if unpack(y, x):
use x
The compiler simply camnot understand that |
The main problem with var parameters here is that the result can be a null pointer dereference; with an out parameter there is no such possibility. The main purpose of Option types is to avoid this behavior. Does anyone seriously want to use code that might cause undefined behavior ? Example of null pointer dereference: import std/options
type
Test = ref object
val: int
Obj = object
x: Test
proc unpack(
self: Option[Test],
val: var Test): bool =
if not self.isSome:
false
else:
val = self.get()
true
proc use(t: Obj) =
echo t.x.val
# deref of null pointer,
# program crash
var a = Obj()
if unpack(none(Test), a.x):
discard
use a With out params there will be no undefined behavior so out params keep code safely.
var conf = Config(meaningOfLife: 42)
if getNone().unpack(conf.meaningOfLife):
discard
echo conf.meaningOfLife # --> 0 but you can write var conf = Config(meaningOfLife: 42)
var meaningOfLife: int
if getNone().unpack(meaningOfLife):
conf.meaningOfLife = meaningOfLife
echo conf.meaningOfLife #--> 42 this is not so bad, given that the code remains safety, but for this particular case proc `?~`[T](val: var T, o: Option[T]) =
if x ?= o:
val = x
config.mearningOfLife ?~ newMearning |
That is true, but this is more of a flaw with proc unpack[T](opt: Option[T], value: out T): bool =
if opt.isSome:
value = opt.unsafeGet
true
else:
when not isInitialized(value): # Imaginary function which returns whether Nim has detected that `value` is initialized
value = default(T)
false
Well this is an obvious usage error, you're looking at a field outside the |
I'll just suggest the following signatures. The latter of which is safer than the proposed "swap to out" as it forces the user to define what happens when there is no value and it allows the use of strictdefs.
|
I said that for ptr and ref objects you need to use a constructor instead of default. But this is not so important, as for me the best solution is |
There's two ways to approach this problem. One is to overhaul The second way is a very, very stupid one. proc unpack*[T](self: Option[T], val: out T): bool {.inline.} =
if not self.isSome:
# if the value is nil/not initialized, make it the default
# otherwise, just do a re-assignment, which sounds incredibly stupid.
if val == nil:
val = default(typeof(val))
else:
val = val # does this actually make `out` happy???
return false
val = self.get()
true |
The third way |
We also have to remember that not all outputs need to be mutable. But yeah, this seems like a better idea. |
Okay, so the latest commit adds the |
Tests are red, please investigate. |
Fusion no longer compiles. Weird. I'll investigate that tomorrow and fix it. |
As a matter of fact, that file doesn't even import options or use them! |
Should work now, in theory 😅 |
Nope, broken yet again. I'll just remove |
@Araq Is there anything else left now? |
I looked at your commits with the template `?=`*[T](self: Option[T], x: untyped): bool
# but should be
template `?=`*[T](x: untyped, self: Option[T]): bool also please do not use early return when it is not needed if not self.isSome:
val = default(T)
return false
val = self.get()
true Should be this: if not self.isSome:
val = default(T)
false
else:
val = self.get()
true |
the problem is caused because fusion uses untyped for its if x ?= some(42):
echo x you need to use untyped{ident} to avoid conflicts like this: template `?=`[T](x: untyped{ident}, y: Option[T]): bool =
var x: T
unpack(y, x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no copying inner option type (and runnableExamples fix)
lib/pure/options.nim
Outdated
## Otherwise, returns false and no value is created. | ||
runnableExamples: | ||
let container = some(1337) | ||
var store: int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var store: int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you force-push this one? I'm not on my computer now.
lib/pure/options.nim
Outdated
|
||
if store ?= container: | ||
assert x == 1337 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var x: T |
Can't we have this |
What about moving it into let x = some "woops"
var y: string
if x.unpack(y):
assert x.get() == y
if z ?= x:
assert x.get() == z |
This PR adds a new procedure to
std/options
that takes in anOption[T]
and avar T
, and if theOption
is not empty, then it stores its contents into thevar T
and returns true. If it is empty, it simply returns false. I've also added a test for this. It works a bit like this: