Rust Scripting

Hi all again,

I do not know maybe this topic was already discussed ... but what if we extend Rust with top-level code, commands ...

Instead of writing:

fn main() {
   println!("Hello Den !!");
}

we would write something like this:

println!("Hello Den !!");

or even like this:

println!("Hello Den !!");

fn main () {
   // Some logic
   println!("In main !!");
}

println!("After main !!");

it will produce the following:

>> Hello Den !!
>> After main !!
>> In main !!

And it will be possible to call it like this:

rustc ./main.rs
1 Like

There is cargo script which will compile and run a file for you, but that doesn't address your desire for loose top-level code.

4 Likes

There's an argument to be made that rust simply isn't the right language for scripting.

This is due to the complexity of the language, the explicitness of the syntax, and the "no shortcuts" approach to safety and error management. The long compile times also don't help for the scenario of scripting.

What I personally do instead is take Rust as far as writing utility binaries, but the top level glue is, and will likely always be, bash shell script.

10 Likes

Using Rust as a scripting language may be possible, but it would take a different top-level program to run it (not rustc), and that top-level program could then easily handle shortcuts like this by wrapping the code (much as rustdoc examples do). I would suggest taking a look efforts towards a Rust REPL, and contributing towards those.

6 Likes

Just throwing this out there: "MiriScript"

3 Likes

cargo-script or a similar tool could be extended to auto-generate a main wrapper if the script does not already contain a main function. This is what rustdoc already does for running code found in doc comments.

On a separate note, with my moderator’s hat on: @redradist, please note that the bar for changes to the Rust is very high. Before posting proposals, please consider looking more closely at alternatives and prior work, including ways of solving the problem outside of the core language. Thanks!

10 Likes

Okay, got it

This is actually my favorite solution. It tangentially builds on what @mbrubeck said in 2021 edition? - #50 by mbrubeck about MIRI become standardized, so if someone wanted to build a rust-like scripting language that had a JIT to MiriScript, it would be possible and long-lasting.

3 Likes

this might help you

install it with cargo install evcxr_repl Then run with: evcxr

2 Likes

Okay, but I cannot figure out why we cannot implement scripting in Rust if there are a lot of crates that shows the need in such feature ?

Even C# already have Top-Level Statements and C# now seems like scripting language ...

It's not a question of whether or not it's possible, it's a cost/benefit analysis. There are many things that could be done, but not enough resources to do them all. Beyond that, before a new feature is added we also need to see what kind of churn it would cause (see @mbrubeck's comment) . For example, if rust scripting becomes a thing, does that mean that you can ship rust scripts? If so, does that mean you need to ship a rust interpreter with your code? What happens if a package manager doesn't ship the latest version of the interpreter (like how for a while some operating systems only shipped with python 2, but there was python 3 code out in the wild, forcing end users to figure out how to install python 3 on their own)?

So, before getting into how this change would happen, please outline what the benefits are. Ideally, write a post like the following:

  • What you want to add/change. Be concrete here, the more concrete the better.
  • What the projected costs will be.
    • Churn
    • Developer confusion - what they should ship, what they support
    • Any other risks you can think of
  • Benefits
  • The reason why the benefits outweigh the costs, and how you came to that conclusion

This will allow the community to make a more informed decision on the proposal.

4 Likes

@ckaran Rust scripting is possible to do just with top-level statements !! After this feature it will look like a script and I as developer do not care that those top-level statements just added in separate generated function then code compiles as usual and than run ...

It could be done just like C# did :wink:

OK, so now we're starting to get into what you want to do (being concrete in your proposed changes). So now do the rest; what will the project costs of the change be? What will be the benefits? How do the benefits outweigh the costs?

You need to convince all of us that this is a worthwhile project to pursue; as it stands, it sounds like a neat idea, but I don't know why I should care. How is this going to make me more productive? How is this going to solve a problem that I have?

Just to be clear, I'm not against what you're proposing, but you haven't yet given me a reason why I should care. On the other hand, I can immediately see the costs:

  • Requires changes to the core language (churn, increased complexity, developer resources to implement & document the feature).
  • Requires users, like me, to learn the new feature, and to not be surprised when it shows up.
  • others?

So, rather than telling me that it's easy to do, tell me why I need this feature. That is the key point that is missing from your statements right now.

4 Likes

I would like to point out that if you can write

if (rand::random()) {
  println!("Hello world");
} else {
  println!("Goodbye cruel world");
}

at top level, which is obviously necessary for top-level "scripting-like" code to be useful, then it's going to seem like you ought to also be able to write

if (rand::random()) {
  fn msg() { println!("Hello world"); }
} else {
  fn msg() { println!("Goodbye cruel world"); }
}
msg()

and that has implications for the entire language that would have to be designed out very carefully. (Constructs like this are uncommon but normal in languages like Python; they're what you do instead of #[cfg] annotations.) I'm not saying I don't like the idea, I'm just saying it's a bigger change than you may have realized.

4 Likes

I don’t think this is a valid concern. It doesn’t fit in with Rust’s scoping rules. Your code

ought to fail because msg is not visible outside the blocks of the if branches, just like it already does when this code is inside the body of a function.

6 Likes

Let me rephrase: if we added the feature OP requested, users would immediately want the scoping rules to be changed so that msg was visible outside the blocks of the if branches, because they would want to use this kind of if expression instead of #[cfg].

1 Like

I think that a more if-like syntax for cfg is an idea that’s entirely orthogonal to top-level statements, especially since cfg attributes don’t even need to appear on top-level, but can just as well be added to blocks and statements etc. inside functions.

By the way: differentiating between compile-time and run-time is important here, hence I’m saying “if-like syntax” since a cfg-alternative would (in my opinion) never actually get the same syntax as an ordinary if. Also, this scripting idea that this thread is about is still about run-time. The top-level statements would be syntactic sugar for a main function, they are not executed at compile-time.


And for some remarks on the original topic:

The last point (above) is actually quite unfortunate. Having two options, either an fn main() or statements on top-level (or both?!?) is confusing and unnecessary. If statements on top-level would be the only option (i.e. there is no explicit main function), which is AFAIK something that other languages that have this kind of “scripting” capability do, then the story would be different, but of course removing the main function will never be a change to Rust that anyone wants to do.

Another disadvantage is that a main function can only be in a single file in your project, and if you replace main with top-level statements then those will just as-well only be allowed in a single file, which is a pretty weird quirk when a single file in your project follows a significantly different syntax than all the other files.

And I don’t see any advantage at all either. For starters, for using Rust for scripting the syntax is the least important bit. Currently, the rust compiler or official tools (as far as I know) offer no support at all for executing scripts, any external tool can define its own special syntax, where special syntax is probably needed anyways e.g. for specifying dependencies, etc.

And last but not least, in my personal opinion it is no fun at all to be having to read the whole script from top to bottom just to find all the top-level statements that together make up the main function. Writing a main function in Rust is super-easy, too. The fn main(){ plus a } at the end is just 11 characters overhead; most comments like “// main program starts below here” in a top-level scripting language that spare the reader of searching the whole file for the main code are longer. [Compare this to e.g. Java where you need the whole dance of doing: public class XYZ { public static void main(String[] args) { … } }.]

2 Likes

The biggest issue not yet addressed: how does this work for library crates.

The obvious answer is "it doesn't", because Rust has no life-before-main or otherwise have any "module constructors". (Barring nonportable linker hacks.)

But then the above example

If this were to be allowed in main.rs, why is it not allowed in any other file?

It's also interesting to note that in python, it's good practice to do

def main():
    pass # whatever should be done for `py ./script.py`

if __name__ == '__main__':
    main()

rather than just including any "main script" instructions at the root level, so that you can import the script as well as just execute it.

Add to this the fact that any "real" script will need access to cargo packages and it more and more looks like you should be using cargo-script, which IIUC you can set up to wrap everything in a fn main() and get "top level expressions" that way.

7 Likes

in C#, top-level statements save you quite a bit of boilerplate (a class and a static function) and 2 levels of indentation. The benefits of top-level statements in Rust would be much smaller.

7 Likes

Note that there is also runner.

1 Like