Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upimplement a proper REPL #655
Comments
steveklabnik
added
the
A-wishlist
label
Jan 21, 2015
alexcrichton
added
the
T-dev-tools
label
May 18, 2015
This comment has been minimized.
This comment has been minimized.
JanLikar
commented
Sep 8, 2015
|
This would be really nice to have. |
This comment has been minimized.
This comment has been minimized.
murarth
commented
Sep 8, 2015
|
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. |
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Oct 23, 2015
|
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? |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Oct 24, 2015
|
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 comment has been minimized.
This comment has been minimized.
murarth
commented
Oct 25, 2015
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
|
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Oct 25, 2015
|
"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 |
This comment has been minimized.
This comment has been minimized.
murarth
commented
Oct 25, 2015
Well, a REPL can't go around changing Rust semantics. If a shadowed binding contains a value that implements |
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Oct 25, 2015
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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:
|
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Oct 27, 2015
|
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? |
This comment has been minimized.
This comment has been minimized.
chkno
commented
Feb 5, 2017
|
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:
|
This comment has been minimized.
This comment has been minimized.
jacksonloper
commented
Mar 15, 2017
|
I dig it |
This comment has been minimized.
This comment has been minimized.
Miuler
commented
Aug 8, 2017
|
Please add a REPL |
This comment has been minimized.
This comment has been minimized.
lowks
commented
Jan 10, 2018
|
|
This comment has been minimized.
This comment has been minimized.
xavier83
commented
Feb 4, 2018
|
Any progress towards implementing a rust REPL this year? |
This comment has been minimized.
This comment has been minimized.
|
I think this sort of project is outside scope of Rust core... should be a community project. |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Feb 4, 2018
|
I'd expect that's especially true since it builds on miri which is being integrated for const evaluation. |
petrochenkov
removed
the
A-wishlist
label
Feb 24, 2018
This comment has been minimized.
This comment has been minimized.
Hoeze
commented
Jun 4, 2018
•
|
Any updates on this? |
This comment has been minimized.
This comment has been minimized.
|
@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 ;) |
This comment has been minimized.
This comment has been minimized.
Mic92
commented
Jun 4, 2018
|
I have build an embedded repl for C++: https://github.com/inspector-repl/inspector |
This comment has been minimized.
This comment has been minimized.
jonathan-s
commented
Feb 9, 2019
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
steveklabnik commentedJan 21, 2015
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.