8

I'm experimenting with Rust. I want to compile a program, and only if it succeeds, run it. So I'm trying:

rustc hello.rs && hello

But hello.exe always runs, even if compilation fails.

If I try

rustc hello.rs
echo Exit Code is %errorlevel% 

I get "Exit Code is 101".

As I understand it, the only truthy value is 0 in cmd, which 101 is clearly not, and && is lazily evaluated, so why does it run hello?


rustc.bat looks like this:

@echo off
SET DIR=%~dp0%
cmd /c "%DIR%..\lib\rust.0.11.20140519\bin\rustc.exe %*"
exit /b %ERRORLEVEL%
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Your executable is not a standard Windows command so testing it is not feasible - but the technique works properly: `echo A|find "a" && echo hello` – foxidrive Jul 05 '14 at 07:18
  • @foxidrive: I don't understand. `rustc` is "not a standard Windows command"? Why does it have an errorlevel then? And your example, where do I sub in `rustc hello.rs`? – mpen Jul 05 '14 at 07:20
  • @Mark, is rustc an executable or a batchfile, can you run `rustc.exe hello.rs`? – wimh Jul 05 '14 at 07:23
  • @Wimmel Well yes, technically I can, but I think you're right -- `rustc` refers to a bat, `rustc.exe` works as intended. – mpen Jul 05 '14 at 07:35
  • Mark, I intended my comment to mean that because we don't have `rustc` then we can't test your problem to see why it fails. – foxidrive Jul 05 '14 at 07:38
  • 1
    Can you modify RUSTC.BAT to include `exit /b %errorlevel%` directly after your `rustc.exe` ? Without `rustc.bat`, it's a guessing game, but that might cause `&&` to work as you'd originally expected. – Magoo Jul 05 '14 at 07:42
  • @Magoo - That won't help. See [my answer](http://stackoverflow.com/a/24586555/1012053). I was shocked by the behavior! – dbenham Jul 05 '14 at 12:45
  • @foxidrive - See [my updated answer](http://stackoverflow.com/a/24586555/1012053). Your deleted answer was a major influence in helping me discover the complexities of this weird behavior. – dbenham Jul 05 '14 at 13:56
  • @Magoo It does end with that. See updated question. – mpen Jul 05 '14 at 16:41
  • @dbenham: Well, I'm completely confused. we have `rustc hello.rs && hello` always running `hello` - are we talking about the batch or the executable? Then we have `rustc hello.rs/echo Exit Code is %errorlevel%` - now this **must** be the executable, else execution would be transferred to the batch (and with the amendment, exited) - the echo` would never be reached. As I've demonstrated, `calling` an external batch recognises the return errorlevel value established by `exit /b` - this conflicts with dbenham's results. I'm going to bed! – Magoo Jul 05 '14 at 17:21
  • @Magoo - You missed the point of my explanation - EXIT /B does not work properly if CALL was not used. I've added test scripts to demonstrate the issue. – dbenham Jul 05 '14 at 17:53

2 Answers2

11

Very curious this. Put a CALL in front and all should be fine.

call rustc hello.rs && hello

I don't totally understand the mechanism. I know that && and || do not read the dynamic %errorlevel% value directly, but operate at some lower level. They conditionally fire based on the outcome of the most recently executed command, regardless of the current %errorlevel% value. The || can even fire for a failure that does not set the %errorlevel%! See File redirection in Windows and %errorlevel% and batch: Exit code for "rd" is 0 on error as well for examples.

Your rustc is a batch file, and the behavior changes depending on if CALL was used or not. Without CALL, the && and || operators respond only to whether the command ran or not - they ignore the exit code of the script. With CALL, they properly respond to the exit code of the script, in addition to responding if the script failed to run (perhaps the script doesn't exist).

Put another way, batch scripts only notify && and || operators about the exit code if they were launched via CALL.

UPDATE

Upon reading foxidrive's (now deleted) answer more carefully, I realize the situation is more complicated.

If CALL is used, then everything works as expected - && and || respond to the ERRORLEVEL returned by the script. ERRORLEVEL may be set to 1 early on in the script, and as long as no subsequent script command clears the error, the returned ERRORLEVEL of 1 will be properly reported to && and ||.

If CALL is not used, then && and || respond to the errorcode of the last executed command in the script. An early command in the script might set ERRORLEVEL to 1. But if the last command is an ECHO statement that executes properly, then && and || respond to the success of the ECHO command instead of the ERRORLEVEL of 1 returned by the script.

The real killer is that EXIT /B 1 does not report the ERRORLEVEL to && or || unless the script was invoked via CALL. The conditional operators detect that the EXIT command executed successfully, and ignore the returned ERRORLEVEL!

The expected behavior can be achieved if the last command executed by the script is:

cmd /c exit %errorlevel%

This will properly report the returned ERRORLEVEL to && and ||, regardless whether the script was invoked by CALL or not.

Here are some test scripts that demonstrate what I mean.

test1.bat

@echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)

test2.bat

@echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem This command interferes with && or || seeing the returned errorlevel if no CALL

test3.bat

@echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem Ending with EXIT /B does not help
exit /b %errorlevel%

test4.bat

@echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem The command below solves the problem if it is the last command in script
cmd /c exit %errorlevel%

Now test with and without CALL:

>cmd /v:on
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

>test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1

>test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1

>test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • EDIT - Rewrote my UPDATE – dbenham Jul 05 '14 at 13:53
  • The bat actually does contains both `cmd /c` and `exit /b`; see updated question. It does work with CALL, however. – mpen Jul 05 '14 at 16:45
  • 2
    @Mark - If the script is not CALLed, then the `EXIT /B` command will mask the return code. It only works if the very last command executed by the script is `cmd /c exit %errorlevel%`. Probably the simplest thing to do is to always use CALL when invoking the script. – dbenham Jul 05 '14 at 17:25
1

Quick demo to prove the point:

@ECHO OFF
SETLOCAL
CALL q24983584s 0&&ECHO "part one"
ECHO done one
CALL q24983584s 101&&ECHO "part two"
ECHO done two

GOTO :EOF

where q24983584s.bat is

@ECHO OFF
SETLOCAL
EXIT /b %1

GOTO :EOF

works as expected...

Magoo
  • 77,302
  • 8
  • 62
  • 84