-
Notifications
You must be signed in to change notification settings - Fork 115
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
Array methods for LuaTable have unclear behaviour #11
Comments
I'm not sure either, but I agree that the current behavior is wrong. Using lua_rawlen is particularly wrong, because that section of the Lua 5.3 Reference manual says that the builtin length operator on tables can return any of the borders of the table, so I guess the current behavior is effectively random. I feel like every time I read about lua tables my mental model of how it works changes, for some reason I just can't keep the semantics of Lua tables in my head accurately. I think the least surprising most correct behavior is just to provide a method that returns all the pairs in the table (lua pairs function), and one that returns the contiguous elements starting from 1 (lua ipairs function). Let me summarize this, and tell me if I've gotten anything wrong: Suppose you want to return a data type like Vec<Option> to rust. This is effectively impossible, and we should just not try to support this directly at all. There is no way to ever reliably have nil values in a table, because then the table is not a sequence and so the value of the length operator is completely undefined. I know that you can make the array part of the table a specific length and make sure that the final key is non-nil, and then looping over 1 to #t will give you nil values, but this is not sane semantics and is extremely extremely brittle and surprising and not something we should do. The parts of the API that expect plain sequences to create containers like Vec / Set could do one of two things, either use 'pairs' and ignore the keys, or use 'ipairs', and either one is sort of surprising. I'm kind of leaning towards just using 'ipairs' and being done with it, but it does mean skipping values in non-sequence tables. I don't feel very strongly over one or the other, what seems most sensible to you? I kind of knew this was wrong when I wrote it, so I completely expected this. In Starbound, this fundamental Lua limitation gave us no end of problems. We ended up having a lot of special types specifically for JSON data that had complex metatables that kept track of nil values and distinguished between array and map, and that sort of helped, but I just don't think there's any getting around the fact that certain rust / c++ types are just problematic for Lua. I agree that we should strive to behave in the least surprising way possible. |
I think it's a bit more subtle than this. Storing
If we want to stay in line with Rust's existing Thinking more about the impls for sets, there's this Lua pattern for representing sets: Use a table I'm not sure what's the best way to handle this, but maybe these impls are too confusing and should just be replaced with explicit methods on The same applies to the impl for
Ah yeah I remember working with JSON in Lua. The lib I've used defined a specific value used to represent JSON NULL values (I think |
Okay, that mostly matches my understanding, thank you. I was aware that setting the entry for the key of #t to nil did not change the sequence property, I was referring more to the general problem of having a sequence with nils inside it being nonsensical, because like you said it would have a hole and no longer be a sequence, I should have clarified that I meant an integral entry < len. But yes, lua tables never have "nil values", because setting a value to nil should be semantically equivalent to removing it, like you said. I was NOT aware that having a non-integer key or key < 0 does not mean that the table is no longer a sequence, interesting.
I'm not so sure I agree with that in general. It doesn't seem terribly important for the conversions to be lossless, there are just SO many ways for conversions to be a lossy. If you accept a
It's possible that the FromLua implementation for HashSet and BTreeSet should just not exist, but there should instead be functions on LuaTable for collecting the keys and values of the table. Actually, there's a way to safely do iteration over LuaTable by placing the current iterated value into the registry, and I was going to add the capability to do this, but it's somewhat slower than the way it's currently done. The API is already decidedly not zero cost, so maybe the cleaner API is better overall?
I mean, certainly I could add conversions to / from Thoughts? Edit: Actually, I'm not sure if I can add impls for |
a0e83b3 partially addresses what we talked about, but definitely only partially. I think the result is definitely better than the previous situation, but we should still talk about what ToLua / FromLua conversions should be available and also the naming scheme. I'm going to leave this issue open and not push another cargo version just yet until there are a few more passes on it. |
That commit looks like a step in the right direction, nice work! The only thing I'm unsure about is that the
True, it's losing type information. However it's not losing any information contained in the value (which would be the case when you had a float in there). With floats, even Lua reports errors when you try to do integer operations on them (try So I think your example should not be an error, but cause a coercion from
That's true. a0e83b3 also made the conversion work the way I'd expect it to, so let's keep it. The set conversions, however, I'm still unsure about. I think it's an operation that's rare enough to make the user use Now,
(I want to clearly document how those are meant to be used) |
I agree completely, but I think the problem is actually the name. ipairs in lua returns the index and value pairs, but the indexes are always contiguous from 1, and in rust the obvious way to do that is with enumerate. It's a bit silly to have the method return the index, but I can't decide which one is more sensible, keep the current behavior and change the name to be less surprising, or make ipairs just return the index pair like lua.
I agree, I'll just remove the set conversion impls, there should only be impls when there is a clear obvious single way to do the conversion.
Your description matches what my intuition of what those traits are supposed to do exactly. I'm not sure that currently the behavior for float to int conversions is correct, but I will definitely check on that today and add tests for it. Can you think of better names for create_empty_table / create_table / create_sequence_table and pairs / ipairs? |
Hmm, maybe And perhaps |
f92e21d removes the Set impls and does the renames we were talking about. I definitely don't think it's perfect now, but it's definitely better than it was and it's at least now consistent. Do you mind if I close this issue until we think of more specific changes we'd like to make? |
Sure, looks great, thanks! |
I've noticed that
LuaTable::for_each_array_value
andLuaTable::array_values
have somewhat surprising behaviour. First of all, they don't really match anything in the Lua API (the manual doesn't define what an array is, only sequences are defined). Second, they require the table to exclusively hold natural numbers as keys, something unseen in the Lua API or standard library (which just ignore the non-sequence part of a table).Another unexpected behaviour occurs when iterating over a table with "holes", like
{ [1] = "bla", [2] = "qux", [123] = "oops" }
, then the missing elements are still taken into account and the closure passed tofor_each_array_value
is called 121 times with anil
value. While this is pretty much the only usable behaviour since the key isn't passed to the closure, this isn't quite what I expected (eg.ipairs
would ignore the 123 key and only iterate over 1 and 2).My initial solution for this would've been to change the methods to act on the sequence part of the table instead (and rename them accordingly), however, the current behaviour is used by the
FromLua
impls ofHashSet
,BTreeSet
andVec
, where it does make more sense (we return an error instead of ignoring the non-sequence part of the table). Not sure what's the best way forward for this API.The text was updated successfully, but these errors were encountered: