-
Notifications
You must be signed in to change notification settings - Fork 1.6k
implement a proper REPL #655
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
Comments
This would be really nice to have. |
While it may not meet some expectations one might have of a "proper" REPL, the rusti project is capable of executing most valid Rust code and printing the value of expressions. I do agree that it's worthwhile to attempt to address the technical issues facing a full-featured REPL, but I believe this is a useful tool even in its current state. |
I'm a huge fan of this (especially a jupyter kernel, that would be wow!), but I'm confused about what the desired behavior of such a REPL would be. For one example, the excellent start made by @murarth makes the assumption that
This seems suboptimal. I feel that there would be valid use cases for creating variables, messing with them, inspecting them over several code cells, etc. For a simple example, I'm imagining the following interactive session
This already introduces a lot of questions that I don't know the answer to. Here are a few...
I guess the broader point here is for most languages, a REPL basically pretends you are executing code one line at a time inside one giant function -- or at least that's the mental model for users. But I don't think that mental model will work. I'm wondering what mental model will work... I'm wondering what brighter minds than mine might have in mind... Even if a formal RFC isn't necessary, is it possible that we could put together some kind of document to iron out some of these details? |
A REPL should almost certainly act as if the code entered is the body of a main() function being executed, each evaluated line being a complete expression. There are questions around how to handle
I don't think this was the intent of the comment you quoted, only that they don't persist between REPL sessions. I think everyone would probably agree that having to include blocks to create
iirc the value assigned to
This is already available through the |
I guess the biggest confusion I'm having is that every let declaration in a function corresponds to a new memory slot in the stack frame. I don't see how that is tenable for a REPL. If you type One solution would be to take all variables left in the namespace after a code-cell, throw them on the heap, and throw them back on the stack before the next code-cell executes. But there might be a more sensible way to do it. To address your statements more specifically, what I'm hearing is
|
This is specified behavior in Rust -- it's not a bug. Variable bindings shadowed by successive declarations are not dropped until the scope ends. In a normal Rust program, if you want a new value to replace an old value on the stack, the value needs to be declared as
|
"New memory slot for every let" is definitely correct, and as it should be! I agree. But I don't see how it works for a repl. That's exactly why the story of "a repl is just like a really really long main() function entered one line at a time" may not be a good metaphor for a rust repl. Possibly "all accessible bindings are thrown onto the heap after each code-cell and back onto a new stack frame before each code-cell" would be a better story. Here's the problem. Say you have one struct that allocates a huge amount of memory on the heap. Then you have a different struct you want to use that also has a huge amount of memory. You can't type..
... since new1 and new2 have different types. So you'd need multiple lets...
That's fine for a function; if users are so incredibly worried about ram, they ought to drop the old But in a repl, this is too dangerous; the "function" lasts until the repl closes (thus I'm beginning to doubt that "function" is the right metaphor here!). After the second let, you can't even drop the first one manually anymore. It's not a leak exactly, but, in a repl, in practice, it's a leak! In a repl context, the user now has no way to correct their mistake. So they have to restart the repl if they run out of RAM on whatever tiny embedded system they're playing around in. Lame! Any way you slice it, repl users will expect to be able to rerun a code-cell including As for your second point, I believe you are saying you want to be able to do stuff like
... where the two variables have different types. That is definitely important! But after the second let, we could still have code that says "drop the value associated with the original binding, unless it has moved or is still borrowed." (It would be a no-op in the case above, since the original |
Well, a REPL can't go around changing Rust semantics. If a shadowed binding contains a value that implements |
I quite agree -- the repl shouldn't change rust semantics!! (The matter of the unexpected out-of-order deallocation of shadowed variables is a really a topic for another issue. At this exact moment I'm crazy enough to wonder if we should forbid variable shadowing altogether unless the scoping of the shadow is made explicit by {} or match or such (or, perhaps more realistically, give a warning). Anyway, that's not happening anytime soon. Back to the point...) I don't think we should permanently allocate memory for every In my experience with jupyter, code cells -- especially the ones initializing values -- are rerun on a regular basis. It would be quite annoying if this meant building up an ever-growing list of shadow-variables, and the only way to get rid of them was to restart the repl. Restarting the repl means losing everything you've built up -- the bane of repls everywhere! The question is not whether we should change rust semantics -- we shouldn't. The question is: what's the right mental model for the repl? One choice is "every executed code-cell is appended to a giant function with one giant (ever-growing) stack frame." But that's just one model, and I rather suspect it's not a very good one for rust. I would tentatively propose the "lexically accessible elements of the stack frame go onto the heap after each code-cell, then those elements go back onto a new stack frame before each code-cell" model. I'm not at all sure that I'm right about any of this, by the way :). I would be very interested to hear what @steveklabnik has to say. |
The issue with re-creating a stackframe is references:
Are we sure that keeping large old temporaries around is actually an issue? How often do you produce large amounts of data in a repl? And if it is an issue, would a "soft restart" command that just closes the the existing scope and starts a new one help? Maybe we could just embrace the model of repl history == function body fully:
|
I like your "repl history == function body" example! It's wonderfully explicit, and I think it would be very clear for users. I'm not exactly sure how it would generalize to something like jupyter (e.g. this or this or, more generally, these), but that may not be what you're going for. Even so, I think maybe we can do better. I believe your concern about re-creating stackframes is essentially a technical one, and I believe not too difficult (famous last words!). It's true, as you say: you can't change the memory location of referenced variables, ergo you can't recreate a contiguous stack frame composited from several past stack frames. But there's no law of nature that says stack frames need to be contiguous in memory -- they need to be known at compile time and fixed for the duration of the function's execution. That we can do; we're compiling before every code-cell execution anyway. It might be a bit harder to get llvm to do the most efficient possible register promotion for the outermost scope of the code-cell, but, somehow, that seems like the least of our troubles :). As for whether we actually need to worry about variables that can only be deallocated with a restart... here's the problem I'm running up against. My dayjob has me 40 hours a week in front of a jupyter console hooked up to 32 gb of ram. When I need it, that console is connected, in turn, to a cluster of ten other ipython kernels (fundamentally, ipython kernel = remote-programmable repl) with access to about 100gb or so. All of these guys are loaded up with data structures, many of which are a pain to recompute (read: several hours). I find myself implementing lots of gratuitous serialization/deserialization code and running a special database -- all because I'm afraid that I might have to restart the repl. Part of my original interest in rust was that I got sick of numpy memory leaks which forced me to restart my cluster. So, to answer your question, if a soft-reset could somehow preserve all of the lexically accessible data while clearing everything else out, then yes that would solve my issue. What I'm hoping for is an extremely safe, industrial grade repl environment that never needs to be restarted. To me, that kind of reliability is what make rust awesome. But I don't know what the community as a whole is looking at; it may be that what we really need is a tool to help new rust users learn the ropes and explore the language. In that case, putting in the time to make it difficult to create permanent unrecoverable memory leaks isn't so important. But don't you just hate memory leaks? |
Responding to the call to propose alternate models: Another possible model is chained functions, with one line of user input per function. The interaction:
Would have the semantics of:
with "become" from #271 to prevent unbounded stack growth. https://internals.rust-lang.org/t/pre-rfc-explicit-proper-tail-calls/3797 specifies how lifetimes of passed and not-passed things are handled. In this example, the first value of a is lost (and its lifetime ends) when f2 invokes f3 without passing it. The semantics of pausing to read the next line are:
is as if this function is currently running and is blocked waiting for input:
|
I dig it |
Please add a REPL |
👍 |
Any progress towards implementing a rust REPL this year? |
I think this sort of project is outside scope of Rust core... should be a community project. |
@tshepang perhaps initially as with rustfmt, rls, etc.. But a good REPL like the Glasgow Haskell Compiler's REPL ghci will require tight integration with the compiler infrastructure and should in my opinion be a core goal in 2019. |
I'd expect that's especially true since it builds on miri which is being integrated for const evaluation. |
Any updates on this? |
@Hoeze I agree with your sentiment, but there are a bunch of compiled languages with good REPLs. Let's not forget that. Of course, the imperative to not be worse than those languages becomes greater due to this fact ;) |
I have build an embedded repl for C++: https://github.com/inspector-repl/inspector |
@murarth What would you say are the underlying requirements to move this forward? Is it possible to implement right now? If it isn't it would probably be good to state the underlying requirements so those can be worked on first. |
Try out https://crates.io/crates/evcxr_repl, see if it works well for you. Also, https://crates.io/crates/runner can work for quick scripts or snippets. |
Any luck with an official REPL so far? |
After so long... Any updates? This is very useful when developing algorithms (e.g. with Python we have REPL so things are much easier)! |
Currently, it's not possible to have a See these issues: |
Naive question here: would a rust REPL be something like Julia? Or am I far off topic? |
So, no GHCi (Haskell REPL) experience in Rust? |
|
evcxr is very slow when compared to something like Cling plus, any updates? |
Thursday Oct 17, 2013 at 02:38 GMT
For earlier discussion, see rust-lang/rust#9898
This issue was labelled with: A-an-interesting-project, E-hard in the Rust repository
It shouldn't re-compile and re-run the entire input so far with each new addition, and should be able to recover from a compilation error or failure. This should likely be implemented in a separate repository until it's solid and has a working test suite.
A good basis for the design would be the cling REPL for C++ built on top of libclang using LLVM's JIT compiler.
The text was updated successfully, but these errors were encountered: