Comments (6)
Hey, thanks for taking the time to understand this issue and take a stab at it! I think what you wrote is safe or at the very least could be made safe. Pretty cool!
I hadn't thought about this approach before, and I do believe it will work, but it still has a pretty big limitation, namely that the T
type you pass to borrow_userdata
must still itself be 'static. This change does allow you to mutate a UserData type inside a call to scope, but crucially it still won't allow you to do things like let Lua interact with non-static handles, such as Mutex locks or RefCell borrows.
This is interesting though, I had been approaching the problem as though allowing non-'static UserData types was the key important feature, rather than mutable references to 'static UserData. Maybe I'm wrong about this?
Unfortunately (as you probably already know) if you use this approach to try and lift the 'static restriction, you will still run into the issues described in the comment you mentioned: #20 (comment)
I've been trying to think of the best way of allowing fully non-'static userdata and I think the best way is simply a simpler, parallel API that is just a more convenient way of doing what I described in that comment, using non-'static functions to forward to ScopedUserData
methods (or something that is equivalent but maybe slightly faster). You need a parallel API because like I explained in the comment, AnyUserData
becomes completely unusable with non-'static userdata.
The ScopedUserData
API I'm proposing would be a simpler version of UserData
without methods that didn't accept &mut self as the first parameter, and a simple implementation of it would simply use a single scoped function as the __index
metamethod, forwarding arguments to appropriate ScopedUserData
methods, exactly as you would do if you were doing the "encode everything as a function" trick yourself. The downsides are that this would be potentially slow, but maybe not as slow as I'm thinking?
With your proposal though, now perhaps there's more to think about. Which of these APIs do you consider MORE useful, one that allows for non-'static userdata but is potentially slower and more complex with a separate API, or one that only allows for mutable references to 'static userdata but uses the same API? I personally ran into this problem dealing with Mutex, RwLock, RefCell etc so obviously I would choose the former, but maybe other people have different needs?
from rlua.
I guess I was a bit confused about the distinction between Lua borrowing values for a lifetime (vs. owning) and implementing UserData for non-'static
types. It is a bit clearer to me now.
There might be different styles of organizing Rust code which require more or less of storing references inside structs (which is the main cause for non-'static
lifetimes?). I have tried to organize my data so that everything is owned, using array indices, string keys, slot maps, or similar to let also references be "owned" in the Rust sense, so that everything is plain old hierarchically owned data.
Thinking about the Rust / Lua layer separation in an application, my approach is that Lua is a kind of user interface, which means that all the tricky resources stuff (graphics, networking, etc.) is handled by Rust code which only confers with Lua to incorporate the user's wishes. This could also mean, in some cases, to reify the Lua method calls into command objects. For example, if the Lua code calls load_image("i.jpg")
, then this doesn't necessarily run code to load the image, it might instead add some enum value LoadImage("i.jpg")
to a queue of things to be processed later.
In this style, I think non-'static
types can often be kept apart from Lua. If your relationship between Rust and Lua is more intimate, then this style might be limiting.
For mutex (and similar) specifically, if it contains a T which is 'static, then Lua can borrow the value itself instead of the lock, using a Scope
which the lock outlives.
fn update(lua :&Lua, value :Arc<Mutex<MyData>>) -> LuaResult<()> {
let mut value = value.lock().unwrap();
lua.scope(|scope| {
lua.globals().set("x", scope.borrow_userdata(value.deref_mut())?)?;
lua.eval::<()>("x:increment()", None)?;
Ok(())
})?;
Ok(())
}
from rlua.
In general I agree with you, but I think forbidding non-'static UserData is just really limiting and weird, especially when the API sits right next to a similar feature (scoped callbacks) that doesn't have this limitation.
You're right that doing the borrowing outside of Lua and passing in direct references is a simpler pattern that could work, but there are lots of ways that pattern can become impossible. For example, in thread you linked to, I was responding to somebody trying to use rlua to make an API to specs, and this is (naively) impossible without non-'static UserData. This will be impossible in any API that either hides the fact that it contains a MutexLock / RwLockWriteGuard etc or doesn't give you some way of deref-ing to a 'static T you can implement UserData for. Even if you can get a &T for some 'static T, you often can't implement UserData for T directly due to coherence rules, and wrapper types are impossible because you can't have them be non-'static.
I mean, again you CAN actually do this because you can just make your entire API using nothing but functions, which I've done before to wrap a complex specs-like API, but it's really gnarly and involves basically passing everything through a "dispatch" function.
If I made a parallel ScopedUserData
API that was similar to UserData
, but ScopedUserDataMethods
only had the add_xxx_method
variants and not the add_xxx_function
variants, would that be acceptable for your use? After thinking about it for a bit, I believe I can make an API that's not awful and is not terribly slow at least. Having a parallel API is an unwelcome complication, but the more I think about it the more I realize that the entire way that the UserData API is designed (to be fast and re-use userdata metatables) is fundamentally incompatible with non-'static UserData, so it DOES at some level make sense to have two APIs for the two "techniques".
The major downside that I can think of of the ScopedUserData
approach is of course that you can't get your type back out of AnyUserData
, so you won't be able to, for example, implement a RHS metamethod.
I'm interested in your opinion. If you had such an API would you find the inability to get the type out of AnyUserData
too limiting? Would you want a borrow_userdata like API in addition to this?
from rlua.
A more general solution for non-'static
types would certainly make more sense and solve the whole problem (consider my proposal a workaround for the specific case of &mut T where T : 'static
).
ScopedUserDataMethods
only had theadd_xxx_method
variants and not theadd_xxx_function
variants, would that be acceptable for your use?
So far I have only had use for add_method_mut
.
two APIs for the two "techniques".
This makes some sense, because the use cases for Lua owning Rust data (GC'ed by Lua) and Lua borrowing Rust data (or owning for a specific lifetime, so no Lua GC in a sense) seem quite different.
would you find the inability to get the type out of AnyUserData too limiting?
I don't immediately see this as a problem.
Would you want a borrow_userdata like API in addition to this?
As long as it is possible to implement the example in my first post without much more code, that's good. I don't think one would often need to use a type as both UserData and ScopedUserData, so having seperate traits and impls for the two would probably work out fine.
from rlua.
Cool, that's all good to hear, it doesn't sound like there's too compelling of a use case for both APIs assuming we can get the more general one working acceptably well.
Which is good, because I've been working on this for the past 2 days or so, and I think I may have actually found a pretty good general solution.
I haven't tested it yet, but I have a new branch where I've managed to make a non-'static userdata API that actually doesn't require a new separate trait. One big downside is that it's a somewhat annoyingly large API change, but it's not too ad. Also, you still of course can't get your non-'static types out of an AnyUserData
, but I think at least that all of the userdata methods will still be called in the same way as regular userdata, and it will otherwise have exactly the same behavior. The implementation is downright terrible, but the API at least is simpler than I was fearing it might be.
As soon as I add tests, I think I'm going to open a PR and we can discuss it more, but before that you can check it out here.
from rlua.
There's now PR #86 for you to take a look at. Making a PR so I can get some feedback.
from rlua.
Related Issues (20)
- Possible bug with __newindex not called on numeric indexing HOT 4
- Question: How to exec multiple things at once? HOT 4
- Segfault on clone of #[repr(C)] userdata subfield HOT 9
- ios build error HOT 7
- examples for error handling HOT 2
- Example for eval return a struct? HOT 1
- Cannot load DLLs in v0.19 HOT 6
- Missing mutable borrows for Table<'lua> HOT 2
- Context.create_sequence_from should support references HOT 1
- Undefined symbol lua_checkstack HOT 8
- Crash on empty source HOT 2
- custom runtime path HOT 3
- creating a `require`-able module HOT 8
- [Request] UserData::from_lua HOT 1
- Suggestion: proc_macro for LuaUserDataMethods::add_method HOT 3
- No documentation on how to use LuaRocks with rlua HOT 3
- Rlua + Luarocks Modules -> Fail to load HOT 3
- Memory leak when using system-luajit HOT 2
- Merge with mlua HOT 32
- [Typo] Example says laoded(instead of loaded) HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rlua.