<p>C/C++ has an entrypoint <code>int main(int argc, char **argv);</code>, which provides the program with the arguments passed to it and a way to signal back the result:</p>
<pre class="lang-c prettyprint-override"><code>#include &lt;stdio.h&gt;

int main(int argc, char **argv) {
    // Or it could be int main(void); if the arguments are not needed
    for (int i = 0; i &lt; argc; i++) printf(&quot;%s\n&quot;, argv[i]);
    return 1;
}
</code></pre>
<p>More &quot;modern&quot; languages such as Java and C# still provide the arguments, but hide away the result behind an explicit call to exit with a specific exit code:</p>
<pre class="lang-java prettyprint-override"><code>public class Main {
    public static void main(String[] args) {
        // C# main is usually void Main(string[] args), but any combination of
        // int|void Main(string[] | void) is allowed.

        for (String arg : args) System.out.println(arg);
        System.exit(1);
    }
}
</code></pre>
<p>Rust and Go go even further and hide the <code>argv</code>, which makes me go out of my way to get the arguments and return the result:</p>
<pre class="lang-rust prettyprint-override"><code>fn cmain(argv: Vec&lt;String&gt;) -&gt; i32 {
    for arg of &amp;argv {
        println!(&quot;{}&quot;, arg);
    }
    return 1;
}

fn main() {
    // Rust main is usually fn main() -&gt; ();
    // It can be fn main() -&gt; Result&lt;(), E: std::fmt::Debug&gt;;
    // but that still doesn't allow user-specified exit codes
    // and more about supporting Rust's func()? syntax
    std::process::exit(cmain(std::env::args().collect()));
}
</code></pre>
<p>Some languages such as Python and Javascript do not have the concept of a main function, thus they need explicit calls to get arguments and return results. I find myself writing a main function anyway when using those languages:</p>
<pre class="lang-typescript prettyprint-override"><code>function main(argv: string[]): number {
    for (const arg of argv) console.log(arg);
    return 1;
}

process.exit(main(process.argv.slice(2)));
</code></pre>
<p>Why do modern languages stop exposing the arguments and exit code through the main function? It's clear they do support the concept of arguments and exit codes, so I guess it's the design decision reasonings that I'm curious about and cannot find.</p>

<syntax><language-design><design-choice-comparison><syntactic-sugar>
Why do "modern" languages not provide argv and exit code in main?
CC BY-SA 4.0
2024-09-09T07:12:14.403Z
In what sense is `std::env::args()` less convenient than `String[] args`? You are only going "out of your way" in Rust because you wrote a separate `cmain` function and called it from the real `main` function. Your simple program would be a lot simpler if you just wrote `for arg of std::env::args()` in the `main` function. Likewise I'm not sure how `std::process::exit(...)` is significantly less convenient than `return`. These are things you will write at most once per project; it's more inconvenient to type `#include <stdio.h>` in C, when modern languages don't hide console output behind ...
kaya3
CC BY-SA 4.0
2024-09-09T07:13:42.517Z
... an import. And arguably `std::env::args()` and `std::process::exit(...)` are *more* convenient than declaring the args as a parameter of `main` and returning from it, because you can call them from anywhere in a program.
kaya3
CC BY-SA 4.0
2024-09-09T07:21:17.870Z
Perhaps a more accurate word would be "shorter", then. `std::env::args()` is longer than `argv`, and `std::process::exit()` is longer than `return`. It could be shortened to `args()` and `exit()`, but then I would need `use std::{env::args, process::exit};` which is also longer than `#import <stdio.h>`.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T07:32:11.157Z
I can buy `std::env::args()` being more convenient to access throughout random places in my program, but I'd argue that's a short term hack - it would be better to collect and parse all the arguments first into an application-specific "state" object, at which point it's no different from using `std::env::args()` directly other than the data being in a more convenient format for my program. Similarly for `std::process::exit()`, I prefer to let upstream handle problems, or bubble it up to main if there's nobody handling it.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T11:32:43.920Z
`std::env::args()` is longer than `argv`, but not longer once you add the parameter declaration `String[] argv`. As for parsing the args and passing them around, absolutely, but your argument parser needs to access the arguments, and it's more convenient for it to fetch them from `std::env::args()` itself than for you to have to pass them along yourself. Particularly, you are probably using a library like [`arg`](https://crates.io/crates/arg) to do the parsing, and that library's API is simpler if it doesn't require you to be a middleman.
kaya3
CC BY-SA 4.0
2024-09-09T12:06:10.527Z
The library API may be simpler but I feel whether it's better is debatable. I'd rather have it take a string array since I feel it's more flexible with regards to how I get those arguments instead of hardcoding the platform's behavior. Perhaps I want to read a config file instead and parse its contents as arguments - [`reflector`](https://archlinux.org/packages/extra/any/reflector/) seems to do that with its config file.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T12:30:24.553Z
If I want to parse from a config file instead, the output will be the same type of config object, but the config file will have a different format, so it needs a different parser.
kaya3
CC BY-SA 4.0
2024-09-09T13:44:39.877Z
For Go, do you feel `os.Args[]` and `os.Exit()` are "hidden"?
RedGrittyBrick
CC BY-SA 4.0
2024-09-09T14:40:43.660Z
My reference point is C/C++, so yes, I consider that "hidden".
404 Name Not Found
CC BY-SA 4.0
2024-09-09T14:51:23.090Z
You can easily emulate the C-style main in these languages, but you can't (to my knowledge) easily do the reverse. That seems like an advantage to me.
JimmyJames
CC BY-SA 4.0
2024-09-09T15:26:50.367Z
It's non-standard (i.e., `-std=gnu17` rather than `-std=c17`), but you can use `void main(void);`, `exit()`, and platform-specific `extern int __argc` and `extern char **__argv` from `stdlib.h` if you really wanted to. A hassle, but it's doable.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T17:44:33.947Z
Worth noting that C/C++ compilers have also varied their main's args, e.g. `char *envp[]`
Kaia
CC BY-SA 4.0
2024-09-09T18:47:19.410Z
Maybe the question is rather "why are modern languages wordier than C", and the answer is "because computers have a million times more RAM and disk space than 50 years ago, so we don't need to sacrifice readability any more to save memory".
Peter - Reinstate Monica
CC BY-SA 4.0
2024-09-10T15:14:06.177Z
C# still does this
ScottishTapWater
<p>Unix was designed to minimize the amount of information a program launching another would need about the program being launched, and vice versa.  The mechanism used when launching programs made it easy to pass a plurality of pointers to strings to a called program, and having <code>main</code> accept those as arguments avoided the need to have anything else in the &quot;permanently loaded' operating system know or care about them.  Programs that would need to access <code>argc</code> and <code>argv</code> outside of <code>main()</code> could have <code>main</code> store them to an external-scope object if desired.</p>
<p>Since that time, the use of general-purpose libraries became more widespread; some such libraries would need to access command-line arguments.  If one such library would expect <code>argv</code> to have be stored into <code>char ***my_arg_values;</code> and another would expect it to have been stored into <code>char **my_arguments;</code>, then <code>main()</code> would have to ensure that <code>argv</code> was written to both such locations.  Having to patch <code>main()</code> in order to use a library was rather a nuisance, so designers of later languages and systems established conventions for global-scope symbols for places where information about arguments would be stored at program invocation.  Different languages and systems would use different symbol names, but a language designer could offer a consistent name for a function that could use whatever system-specific symbols or other means would be needed to retrieve the information in platform-independent fashion.</p>
<p>As for return value, it would have been apparent fairly early on that situations may arise where a program would discover in the middle of a nested function call that it wasn't going to be do anything useful.  While it would have been possible to have <code>main()</code> use a couple of global symbols to perform a <code>setjmp()</code> and allow called code to store the return status before doing a <code>longjmp()</code> back to <code>main()</code>, which could then return the appropriate status code, this would require the called function to know what symbols the author of <code>main()</code> had chosen for that purpose.  Having a call to <code>main()</code> wrapped in a platform/implementation-specific way which can then be exploited by a platform-specific implementation of a function with a standard <code>exit()</code> interface avoids the need for such coordination between the author of <code>main()</code> and code needing to exit.</p>
<p>Once the usable-from-anywhere means of accessing command-line arguments were standardized, the ability of <code>main()</code> to accept arguments directly and have its return value passed back to the system became redundant.  While it made sense for C implementations to continue supporting them, there was no reason to carry that convention into other languages.</p>

CC BY-SA 4.0
2024-09-10T15:22:30.713Z
2024-09-10T15:22:30.713Z
supercat
CC BY-SA 4.0
2024-09-10T18:46:08.317Z
"*anything else in the "permanently loaded' operating system know or care about them*" - I have a feeling there's a word or two missing in that sentence, but I can't figure what it means either way. Are you referring to some sort of static memory allocation? In the program or in the operating system?
Bergi
CC BY-SA 4.0
2024-09-10T21:32:55.293Z
Maybe you can offer better wording--the basic idea is that once the parent process had built up the argument strings and the pointers to them, and called `main()`, the parent process could be jetissoned from memory leaving behind nothing except those strings and pointers.  Having a pointer to the arguments live in a static-duration object would require that the calling program and called program both agree on where that object should be, and having a called program use a function to retrieve arguments would require spending precious RAM to hold that function.
supercat
CC BY-SA 4.0
2024-09-10T23:03:04.000Z
So are those strings and pointers living in the memory of the parent process, in the memory of the operating system, or in the memory of the child process? (Or is it all just shared memory?) Or is your point that it doesn't matter? I don't get what this has to do with the object lifetime though - surely no matter where they are placed, they must be held in memory until the child process ends (if it hasn't shared them further).
Bergi
CC BY-SA 4.0
2024-09-11T14:41:23.473Z
@Bergi: I think the strings would have originally been placed at the top of what would become the stack of the new process.  In the first Unix system, each process would have almost the entire memory space of the computer available, save for a small amount reserved for the operating system.  Switching tasks would always mean storing the memory state of the current process to disk, and loading the memory state of the process to switch to from disk; there was no arbitration of memory usage between applications.
supercat
<h2>&quot;Explicit is better than implicit&quot;</h2>
<p>Philosophically for Python, giving an exit code back to the OS is doing something fundamentally different from passing a result from a called function back to the caller - especially since that code can only be a machine-level integer (of whatever platform-defined size), not a Python object. Indeed, <code>sys.exit</code> already needs to translate whatever's passed to it. Expecting a specific function to have the special behaviour of communicating back to the operating system, just because &quot;it was called first&quot;, is a bridge too far. (Especially given that code actually starts running outside of any function - you could maybe argue that the top-level code &quot;returns&quot; a <code>module</code> object once it's done, but how do you translate that into success or failure, let alone allowing for multiple numbered failure status codes?)</p>
<h2>&quot;Special cases aren't special enough to break the rules&quot;</h2>
<p>Similarly on the input side: the command line has to be parsed, or at least tokenized, and then <em>used somewhere</em>. But there's no specific function earmarked to receive them, because the general rule in Python is that functions are only called when you call them (another kind of explicitness). The only &quot;automatic&quot; running of code is from the top of the file downward - which is necessary e.g. so that <code>import</code>s can happen at runtime.</p>
<p>We might instead imagine the Python startup process assigning command-line arguments to a <em>builtin</em> name, which we could then treat as if it were a &quot;parameter name&quot; for the top-level code in the <code>__main__</code> module. But presumably we'd reject this idea on the same aesthetic grounds as already described. (After all, <code>sys</code> is a builtin module implemented directly by the interpreter rather than by Python code; one might equally well argue for dumping its entire contents into the builtin namespace preemptively.)</p>

CC BY-SA 4.0
2024-09-10T00:11:25.737Z
2024-09-10T00:11:25.737Z
Karl Knechtel
<p>The arguments and return code for <code>main()</code> are handled by the C implementation, not by your operating system. On some level, it's an arbitrary decision that C works this way and there is no particular reason why other languages should work this way.</p>
<p>Here is what your C implementation may actually be doing, more or less (not real code):</p>
<pre class="lang-c prettyprint-override"><code>// _start is the ACTUAL entry point.
void _start(void) {
  int argc = get_argc();
  char *argv = get_argv();
  int status_code = main(argc, argv);
  exit(status_code);
}
</code></pre>
<p>How this actually works depends on the operating system, and every operating is different.</p>
<p>On Linux, the value of <code>argc</code> and <code>argv</code> are stored directly on the stack, so the entry point just has to read those values from the right location and then forward them to <code>main()</code>. You still have to call <code>exit()</code> after main returns.</p>
<p>On Windows, it's a little more complicated. The OS doesn't actually have argc and argv, it has a single string containing the command-line arguments. To get that string, you can call the function <code>GetCommandLineA()</code> or <code>GetCommandLineW()</code> (depending on which encoding you want to use). You can use this string directly, parse it yourself, or parse it using the standard functions <code>CommandLineToArgvA()</code> or <code>CommandLineToArgvW()</code>. So, Windows' version may look something like this:</p>
<pre class="lang-c prettyprint-override"><code>void _start(void) {
  wchar_t *command_line = GetCommandLineW();
  int argc;
  wchar_t **argv = CommandLineToArgvW(command_line, &amp;argc);
  int status_code = main(argc, argv);
  ExitProcess(status_code);
}
</code></pre>
<p>However, there is a pair of <strong>significant</strong> problems here:</p>
<ol>
<li>On Windows, the command line does not include the program name. In other words, <code>argv[0]</code> will be missing.</li>
<li>On Windows, you may want the <code>wchar_t</code> version of the command line. (This is another reason why Rust has <code>std::env::args()</code> and <code>std::env::args_os()</code>--the <code>args_os()</code> version will let you work with <code>wchar_t</code> strings that cannot be encoded as UTF-8. This is a pathological case to be sure, but Rust is designed to give you the tools to handle pathological cases correctly.)</li>
</ol>
<h2>Conclusion</h2>
<p>You can see that with all these differences between different operating systems, it doesn't really make sense to copy the way C does things. C doesn't do it very well.</p>
<ul>
<li><p>In order for your process to exit, on common operating systems, you <em>have</em> to call a system call like <code>_exit()</code> or <code>ExitProcess()</code>. If main returns a status code, it just means that there is some code in your C runtime that takes the result of <code>main()</code> and calls that function.</p>
</li>
<li><p>The format for program arguments is different on different operating systems, and there is not a simple way to unify it all.</p>
</li>
</ul>

CC BY-SA 4.0
2024-09-09T18:12:02.447Z
2024-09-10T19:46:44.297Z
2024-09-10T19:46:44.297Z
Dietrich Epp
CC BY-SA 4.0
2024-09-10T07:23:32.313Z
Great answer. I would just add that the C language was originally created for implementing utilities in the early Unix system. I see that as the reason why the Unix process calling interface is so integrated into C.
pabouk - Ukraine stay strong
CC BY-SA 4.0
2024-09-10T11:47:33.613Z
I have to downvote as the answer is factually incorrect. On linux, you create a process using fork/exec. The exec variants actually do take an argc/argv; the process has an exit status you can query when calling "wait" variants. So the POSIX (i.e. OS standard) process creation API perfectly aligns with the C main API (by design). Why this fact emerges and whether it justifies this API for C programs is debatable (more probably bad design separation as stated in other answers). On windows the command line is indeed a single line, so the answer is more realistic/correct.
Yann TM
CC BY-SA 4.0
2024-09-10T16:11:20.883Z
@YannTM: There is no `argc` parameter to any of the `exec()` family functions.
Dietrich Epp
CC BY-SA 4.0
2024-09-10T16:12:00.093Z
@YannTM: The POSIX standard describes the C interface. That C interface is provided by the C implementation on your operating system—it is not necessary to use the C interface when you are not using C.
Dietrich Epp
CC BY-SA 4.0
2024-09-12T19:33:37.323Z
There is no argc, but the argv parameter in C is guaranteed to have a sentinel 0x0 last argument, so argc is actually not necessary, it's a feature. The args to exec are passed as is to main, so the point stands. The C interface is what the system implements, whether you acknowledge it or not, any process created on posix comes from this system call, you must indeed go through it to create a process.
Yann TM
CC BY-SA 4.0
2024-09-13T13:47:53.897Z
@YannTM: The C interface is provided by the crt, not the kernel. The only way that it is part of the "OS" is if you count crt as part of the OS--which is reasonable, since it's specified by POSIX, but whether you count crt as "part of the OS" is not really the question here. The fact is that other languages (1) run on systems besides Unix and/or (2) run on Unix but without using crt. Besides--most languages don't represent strings the way C does anyway, C is kind of a weird outlier.
Dietrich Epp
<h3>Even in the olden days, <code>main</code> and args varied.</h3>
<p>Interpreted languages and functional languages often don't follow <code>main(char** argv)</code> style. Bash uses <code>$1 $2 $3</code> and executes everything in the outermost code block, like Python does. Perl uses <code>@ARGV</code> at global scope. Lisp/Scheme don't have a main, and don't have unified ways of getting argv. (Because why aren't you running them on a Lisp machine?) Fortran doesn't have a main fn, and argv access is added via functions like <code>getarg</code> and <code>get_command_argument</code>. Haskell's entry point takes only an IO monad, but provides <code>System.Environment.GetArgs</code>.</p>

CC BY-SA 4.0
2024-09-09T18:55:40.107Z
2024-09-09T18:55:40.107Z
Kaia
<h3>Most Programs are not Launched From Command Prompts</h3>
<p>Those that are, are mainly running on Windows, Linux or UNIX, where other languages already fill the niche of command-line utilities.  Since this interface exists, you will occasionally see GUIs configure their shortcuts to pass options on the command line, if it might be invoked different ways depending on which one the user clicks, but even that is rare these days.</p>
<p>Modern languages normally provide some way to get the command-line arguments, but it’s more like how C programs look up environment variables.  It’s no longer assumed that every program is launched by an operator typing at a console or from a script saved as a text file. Command-line arguments are no longer so important that a general-purpose language would have special syntax sugar for them.</p>
<h3>The <code>argc</code> and <code>argv</code> Data Structures are Terrible</h3>
<p>I often find myself telling C programmers that it’s unfortunate everyone learns <code>argv</code> first, because a pointer to pointers to null-terminated strings is a terrible data structure and almost never the best kind of “two-dimensional array” to use.</p>
<p>Because they’re so old, they also let coders shoot themselves in the foot with a lot less warning than even modern C would give them, especially the type system not tracking the size of or preventing modification of any of the arguments or strings.</p>
<p>Modern languages all have some kind of native String type and some kind of generic Vector container, so a modern API would return the list either as a vector of strings or a vector of string views.</p>
<h3>It’s an Awkward Fit for Type Systems</h3>
<p>Languages sometimes allow function overloading, but they don’t normally have a feature that lets a function be called with different arguments depending on how it’s implemented in another file.  That’s not useful in real-world programs.  Even in C, written in part to be low-level enough to implement its own standard library (see “Why Pascal is not my Favorite Language”), there’s never been any demand for something like, hypothetically:</p>
<pre class="lang-c prettyprint-override"><code>#if __extern_function_args(foo, int, char**)
    foo(argc, argv);
#elif __extern_function_args(foo, void)
    foo();
</code></pre>
<p>Even when ANSI C added variadic functions to make it possible to write <code>printf()</code> in C, they didn’t see any need to make it flexible enough to call either <code>int foo(void)</code> or <code>int foo(int argc, char **argv)</code>, even in the same source file.  C and C++ make <code>main()</code> the one special exception to how every other function works.</p>
<p>There’s a lot of weird stuff in C because something happened, by coincidence, to work on the DEC PDP-11 back in its formative years and changing it now would break backward compatibility  There’s not much reason for other languages to do things the same way, other than to imitate C.</p>
<h3>Some Newer Languages do Have Error Returns</h3>
<p>For example, Rust allows <code>main()</code> to return any type that implements the <code>std::process:Termination</code> trait.  The Standard Library allows you to return an <code>ErrorCode</code> of <code>SUCCESS</code> or <code>FAILURE</code>, like <code>EXIT_SUCCESS</code> and <code>EXIT_FAILURE</code> in C.  But this feature really exists to allow the <code>?</code> operator to work in short demo programs that do I/O (by short-circuiting on error and printing a debug message).</p>

CC BY-SA 4.0
2024-09-09T20:45:43.297Z
2024-09-09T21:33:26.230Z
2024-09-09T21:33:26.230Z
Davislor
CC BY-SA 4.0
2024-09-09T20:55:20.850Z
re: point 2, I don't think the issue is e.g. Java's argv being a String[] rather than a null-terminated char**. OP uses the example of writing a custom `cmain(argv: Vec<String>) -> i32` in Rust
Kaia
CC BY-SA 4.0
2024-09-09T22:09:55.737Z
Another bit of weirdness is that the Standard didn't want to require that programs targeting platforms that don't recognize any concept of return codes must nonethelss end with `return 0;`, but didn't want such programs to malfunction on platforms that require that programs return zero to indicate success, so the Standard added a special rule for `main`'s return value.
supercat
CC BY-SA 4.0
2024-09-10T01:33:01.220Z
@supercat This is another one of those things that just happened to work on the DEC PDP-11 running UNIX. It’s also another way that `main()` is a precious snowflake. No other function declared as returning `int` is allowed to omit a `return` statement.
Davislor
CC BY-SA 4.0
2024-09-10T07:26:31.147Z
"not preventing modification of any of the arguments " - why is that an issue? It's perfectly legal in C to modify your arguments. It is a true `char**`. There are reasonable designs which do modify their arguments as they're being parsed, to keep track of parsing state.
MSalters
CC BY-SA 4.0
2024-09-10T09:56:50.050Z
For some reason, the essay [Why Pascal is not my favourite language](http://www.lysator.liu.se/c/bwk-on-pascal.html) is not as easy to find as one would expect, so here's a link.
TRiG
CC BY-SA 4.0
2024-09-10T15:28:51.320Z
@TRiG: Many of the complaints in that essay relate to things that aren't problems in popular dialects of Pascal (and probably contribute to those dialects' popularity).  From a syntactic perspective, I like popular-dialect Pascal better than C except for the use of `;` as a statement separator rather than terminator and the lack of compound assignment operators which could have easily be incorporated into Pascal.
supercat
CC BY-SA 4.0
2024-09-10T16:54:54.573Z
@MSalters For example, K&R C made it very easy to write `if (argc = 4)` and clobber the length of `argv`, and didn’t even give you a way to code defensively against that kind of typo. (Modern compilers will at least warn you about this, unless you write double parenthesis to tell it you really meant to shoot yourself in the foot.) The language also lets you try to modify any of the pointers in `argv` or the strings it points to, but on many implementations, this breaks stuff. The only reason to allow this was that `const` didn’t exist yet when K&R established the convention.
Davislor
CC BY-SA 4.0
2024-09-10T17:27:08.880Z
@MSalters Accidentally overwriting the terminating `\0` at the end of a string is even worse, because now an arbitrary number of bytes afterward become part of the string. Even modern C coding standards lean on the concept of vectors and slices so heavily, they’d bundle and abstract those data structures more than they did. And have some syntax for passing input parameters to a function by read-only reference.
Davislor
<p>Different languages are useful for writing very different kinds of programs, in different domains entirely.</p>
<p><strong>Command-line interface might just not be the main way to interact with programs in a given language.</strong></p>
<p>As you point out, most or all &quot;general purpose&quot; languages do have a way to access argv/argc.</p>
<p>But they don't have to be <strong>at the center of attention all the time</strong>, especially if most programs are not expected to use them.</p>
<p>Also, many languages (like C# you mentioned) actually let you choose whether to use <code>int main(args)</code> or <code>void main()</code>.</p>
<hr />
<p>(edit: adding examples from the comments)</p>
<p>When Java was originally released, it was popular for writing applets that run in the browser. When the program is an applet, the platform is the browser, and it doesn't expect a return value like a regular OS.</p>
<p>Even languages like Go, which seems to be geared for low-level programming and/or CLI/headless applications, can also be compiled to JS or wasm. Putting args and exit into a <code>os</code> package is a way to avoid <strong>tightly coupling one specific entry point design</strong> to the language. When designing a language it's usually a good thing to delegate concerns to libraries. When compiling for an environment without argv/exit, it's nicer to just have a missing <code>os</code> package than to have superfluous handling of fake empty arguments (from the perspective of both the language implementer and of the language user).</p>

CC BY-SA 4.0
2024-09-09T06:15:39.697Z
2024-09-10T06:56:35.250Z
2024-09-10T06:56:35.250Z
Eldritch Conundrum
CC BY-SA 4.0
2024-09-09T06:42:19.267Z
Surely it doesn't cost much to keep access to them more convenient? The OS speaks C's `int main(int argc, char **argv);`, the language just doesn't expose a similarly convenient form. Also, C# is the _only_ language that I know of that allows users to choose, and I only learned about it while researching this question - I've only ever known `void Main(string[] args);`. It's cousin Java, enforces `void main(String[] args);` for main.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T07:04:13.960Z
When Java was originally released, it was popular for writing applets that run in the browser. When the program is an applet, the platform is the browser, and it doesn't expect a return value like a regular OS.
Eldritch Conundrum
CC BY-SA 4.0
2024-09-09T07:15:01.160Z
Fair enough, but then there's Rust and Go which seem to be geared for low-level programming and/or CLI/headless applications, and they don't expose the arguments or return value either.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T10:11:36.727Z
Rust and Go can also be compiled to JS. Putting args and exit into a `os` package is a way to avoid tightly coupling one specific entry point design to the language. When designing a language it's usually a good thing to delegate concerns to libraries. When compiling for an environment without argv/exit, it's nicer to just have a missing `os` package than to have superfluous handling of fake empty arguments (from the perspective of both the language implementer and of the language user).
Eldritch Conundrum
CC BY-SA 4.0
2024-09-09T11:39:29.963Z
I assume by "compiling to JS", you mean targetting wasm? In which case, my quick test of compiling C to wasm with emscripten shows that wasm handles C's main exit codes and arguments correctly, but I guess I can see your point of some platforms not having the concept of exit codes and arguments to main.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T11:39:40.503Z
Maybe it's just me then, but I imagine "fake empty arguments" can be optimized away by the compiler -  I wouldn't mind having e.g., `argc` and `argv` be `0` and `NULL` or even UB to access if the platform didn't have those concepts, making it effectively free.
404 Name Not Found
CC BY-SA 4.0
2024-09-09T13:26:59.583Z
@404NameNotFound: The reason for not passing dummies around is not performance, it's **semantics**. The human developer has enough concerns that they shouldn't need to, on top, wade through dummies.
Matthieu M.
CC BY-SA 4.0
2024-09-09T14:38:45.073Z
@MatthieuM. In that case, I think the C# approach is a good one - it's still there, but users can choose whether to use it or not. Maybe other languages think that's too complicated then?
404 Name Not Found
CC BY-SA 4.0
2024-09-09T15:45:40.280Z
@404NameNotFound: The OS does not speak C's `int main(int argc, char** argv)`. That's a UNIX thing, which co-evolved with C. Windows uses `GetCommandLineW`, which doesn't include `argc`, is UTF-16 encoded, and retains whitespace. It's Visual C++ which wraps that up as `int main(int argc, char** argv)`
MSalters
CC BY-SA 4.0
2024-09-11T20:41:27.810Z
@MSalters: One has to be a little careful with statements like that. On "most" Unixy platforms that are not Linux, libc is considered part of the OS (in the sense that libc is the official, API-stable interface for making syscalls, regardless of language, and OpenBSD goes one step further and actively tries to prevent syscalls from originating anywhere other than libc). It is true if by OS you mean "kernel," but that's a Linux-ism.
Kevin
<p><em>Disclaimer: I will not attempt to go spelunking into the archives of each language's design to figure out the reasons for why each of them decided NOT to follow in C's shoes. Instead, I'll focus on the downsides.</em></p>
<p>Let's answer a slightly different question: <strong>what's wrong with C's design?</strong></p>
<h3>Locality</h3>
<p>From a user point of view, passing the command line arguments to <code>main</code> is mostly useful if those arguments are handled &quot;close&quot; to <code>main</code>.</p>
<p>If, instead, parsing the arguments involves a complex routine, and requires threading said arguments through multiple layers, then a user may prefer Python's or Rust's approach of being able to access said arguments from anywhere.</p>
<h3>Character Encoding</h3>
<p>The problem of <code>char</code> is that... it's not a character. It's a byte. It can adequately represent ASCII characters, but anything further is a mess.</p>
<p>Unix uses... something close to UTF-8, so it works relatively well.</p>
<p>Windows uses something close to UTF-16 instead, and thus would need <code>wchar_t</code> instead of <code>char</code> for passing exactly the arguments passed.</p>
<p>And let's not even talk about embedded NUL bytes...</p>
<p>If you look closer at Rust you'll notice there are <em>two</em> APIs:</p>
<ul>
<li><code>std::env::args()</code> returns UTF-8 strings, which is convenient, but not guaranteed to be able to map all arguments perfectly.</li>
<li><code>std::env::os_args()</code> returns <code>OsString</code>, which perfectly capture the OS-level argument, even on Windows, but may not be convertible to UTF-8.</li>
</ul>
<p>Thankfully, both deal with 0.</p>
<h3>Performance</h3>
<p>The signature of <code>main</code> in C forces <em>every</em> argument to be (1) converted to a <code>char</code> array and (2) stored in memory.</p>
<p>For example, if Rust used <code>fn main(argv: &amp;'static [&amp;'static str])</code>, then it would have to create an array of <code>&amp;'static str</code>:</p>
<ol>
<li>This would occupy <code>argv.len()</code> x 16 bytes (8 bytes length &amp; 8 bytes pointer).</li>
<li>This would require validating that all arguments are, indeed, correct UTF-8 (which Unix doesn't guarantee).</li>
</ol>
<p>If the user only ever checks the first argument, that's a lot of needless work, and needless memory used.</p>
<p>And of course, it gets worse on Windows, where the arguments are not stored in anything close to UTF-8 in the first place.</p>
<p><em>Note: It's not unusual for C++ compilers, or linkers, to be invoked with hundreds to thousands of arguments.</em></p>
<h3>Conclusion</h3>
<p>C's design emerged over 40 years ago, in a very different environment.</p>
<p>Since then the world has evolved, and introduced challenges that the design copes... badly with. Band-aid upon band-aid to kinda make it work.</p>
<p>It's rather expected than 40 years later, with different requirements and the benefit of hindsight, new languages pick a different design.</p>

CC BY-SA 4.0
2024-09-09T17:37:28.733Z
2024-09-09T17:37:28.733Z
Matthieu M.
CC BY-SA 4.0
2024-09-10T07:38:57.647Z
"Unix uses... something close to UTF-8" -- I'm not sure what you mean with "something close", but I don't think too many unix-likes enforce UTF-8 or anything here, but just pass the bytes around. Except that there can't be embedded NUL-bytes, since API uses that as the string terminator.
ilkkachu
CC BY-SA 4.0
2024-09-10T11:30:05.253Z
@ilkkachu: They don't enforce UTF-8 AFAIK, indeed, hence my specific choice of words (and phrasing).
Matthieu M.
CC BY-SA 4.0
2024-09-10T17:13:56.083Z
Re: memory, can you elaborate on how not using C’s design avoids keeping arguments in memory? If a process could request them at any time, that memory has to be allocated somewhere. (But maybe you just meant the extra memory of the pointers and arrays, not strings? If so, that point wasn’t clear to me.)
D. Ben Knoble
CC BY-SA 4.0
2024-09-10T17:21:33.777Z
@D.BenKnoble: In Rust, an iterator is created over the arguments, which creates (and yields) _one_ `String` (or `OsString`) at a time. Thus there's no permanent increase of memory, just an ephemeral one, one argument a time -- unless the user chooses to keep one of the `String` around, but then it's no longer overhead: it's a feature.
Matthieu M.
CC BY-SA 4.0
2024-09-10T17:23:59.273Z
Ok, so I think my parenthetical was the correct interpretation. Thanks.
D. Ben Knoble
CC BY-SA 4.0
2024-09-10T18:55:11.100Z
"*a user may prefer [the] approach of being able to access said arguments from anywhere*" - why is that considered a good thing here? Normally, global variables are considered an antipattern (due to creating implicit dependencies, and even worse if introducing mutable state). Not sure why `argv` should be an exception from this rule.
Bergi
CC BY-SA 4.0
2024-09-11T02:02:18.203Z
Global variables aren't really a problem. The problem is shared mutable state. `argv` is not mutated (unless you are doing something incredibly sketchy) so it is not problematic.
Oscar Smith
CC BY-SA 4.0
2024-09-11T07:02:14.280Z
@Bergi: I did not say that being able to access argv from anywhere is good, I said users may prefer it. I don't consider it particularly good myself.
Matthieu M.
CC BY-SA 4.0
2024-09-11T07:04:42.840Z
@OscarSmith: You are correct that global mutable state is particular problematic, and global constants are not. `argv` is in the middle though: while it cannot be modified, its value is unknown until runtime -- which means you ideally ought to want to test with various values, but as you mentioned cannot really go and modify `argv` to do so. So it's not as clear cut, really.
Matthieu M.
CC BY-SA 4.0
2024-09-11T18:08:17.590Z
You first point, locality, is not "what's wrong with C's design?" but "what did C get right that all of these later languages insist on getting wrong?" Having the arguments be local to `main` ensures that nothing in the program (e.g. library code) can inspect and alter its behavior based on arguments without a chain of license to do so from the program entry point.
R.. GitHub STOP HELPING ICE
CC BY-SA 4.0
2024-09-12T07:45:26.743Z
@R..GitHubSTOPHELPINGICE: Bergi made the same argument. I already answered.
Matthieu M.
CC BY-SA 4.0
2024-09-16T18:59:09.753Z
@MatthieuM.: Bergi's remark is about something different, global state being bad vs library code hooking into and doing unwanted things with command line arguments being bad.
R.. GitHub STOP HELPING ICE
# Why do "modern" languages not provide argv and exit code in main?
Posted by: username_0 on 2024-09-09
C/C++ has an entrypoint int main(int argc, char **argv);, which provides the program with the arguments passed to it and a way to signal back the result:
#include 

int main(int argc, char **argv) {
    // Or it could be int main(void); if the arguments are not needed
    for (int i = 0; i < argc; i++) printf("%s\n", argv[i]);
    return 1;
}

More "modern" languages such as Java and C# still provide the arguments, but hide away the result behind an explicit call to exit with a specific exit code:
public class Main {
    public static void main(String[] args) {
        // C# main is usually void Main(string[] args), but any combination of
        // int|void Main(string[] | void) is allowed.

        for (String arg : args) System.out.println(arg);
        System.exit(1);
    }
}

Rust and Go go even further and hide the argv, which makes me go out of my way to get the arguments and return the result:
fn cmain(argv: Vec) -> i32 {
    for arg of &argv {
        println!("{}", arg);
    }
    return 1;
}

fn main() {
    // Rust main is usually fn main() -> ();
    // It can be fn main() -> Result<(), E: std::fmt::Debug>;
    // but that still doesn't allow user-specified exit codes
    // and more about supporting Rust's func()? syntax
    std::process::exit(cmain(std::env::args().collect()));
}

Some languages such as Python and Javascript do not have the concept of a main function, thus they need explicit calls to get arguments and return results. I find myself writing a main function anyway when using those languages:
function main(argv: string[]): number {
    for (const arg of argv) console.log(arg);
    return 1;
}

process.exit(main(process.argv.slice(2)));

Why do modern languages stop exposing the arguments and exit code through the main function? It's clear they do support the concept of arguments and exit codes, so I guess it's the design decision reasonings that I'm curious about and cannot find.
**Tags:** syntax language-design design-choice-comparison syntactic-sugar
> **Comments:**
> **username_1**: In what sense is `std::env::args()` less convenient than `String[] args`? You are only going "out of your way" in Rust because you wrote a separate `cmain` function and called it from the real `main` function. Your simple program would be a lot simpler if you just wrote `for arg of std::env::args()` in the `main` function. Likewise I'm not sure how `std::process::exit(...)` is significantly less convenient than `return`. These are things you will write at most once per project; it's more inconvenient to type `#include <stdio.h>` in C, when modern languages don't hide console output behind ...  
> **username_1**: ... an import. And arguably `std::env::args()` and `std::process::exit(...)` are *more* convenient than declaring the args as a parameter of `main` and returning from it, because you can call them from anywhere in a program.  
> **username_0**: Perhaps a more accurate word would be "shorter", then. `std::env::args()` is longer than `argv`, and `std::process::exit()` is longer than `return`. It could be shortened to `args()` and `exit()`, but then I would need `use std::{env::args, process::exit};` which is also longer than `#import <stdio.h>`.  
> **username_0**: I can buy `std::env::args()` being more convenient to access throughout random places in my program, but I'd argue that's a short term hack - it would be better to collect and parse all the arguments first into an application-specific "state" object, at which point it's no different from using `std::env::args()` directly other than the data being in a more convenient format for my program. Similarly for `std::process::exit()`, I prefer to let upstream handle problems, or bubble it up to main if there's nobody handling it.  
> **username_1**: `std::env::args()` is longer than `argv`, but not longer once you add the parameter declaration `String[] argv`. As for parsing the args and passing them around, absolutely, but your argument parser needs to access the arguments, and it's more convenient for it to fetch them from `std::env::args()` itself than for you to have to pass them along yourself. Particularly, you are probably using a library like [`arg`](https://crates.io/crates/arg) to do the parsing, and that library's API is simpler if it doesn't require you to be a middleman.  
> **username_0**: The library API may be simpler but I feel whether it's better is debatable. I'd rather have it take a string array since I feel it's more flexible with regards to how I get those arguments instead of hardcoding the platform's behavior. Perhaps I want to read a config file instead and parse its contents as arguments - [`reflector`](https://archlinux.org/packages/extra/any/reflector/) seems to do that with its config file.  
> **username_1**: If I want to parse from a config file instead, the output will be the same type of config object, but the config file will have a different format, so it needs a different parser.  
> **username_2**: For Go, do you feel `os.Args[]` and `os.Exit()` are "hidden"?  
> **username_0**: My reference point is C/C++, so yes, I consider that "hidden".  
> **username_3**: You can easily emulate the C-style main in these languages, but you can't (to my knowledge) easily do the reverse. That seems like an advantage to me.  
> **username_0**: It's non-standard (i.e., `-std=gnu17` rather than `-std=c17`), but you can use `void main(void);`, `exit()`, and platform-specific `extern int __argc` and `extern char **__argv` from `stdlib.h` if you really wanted to. A hassle, but it's doable.  
> **username_4**: Worth noting that C/C++ compilers have also varied their main's args, e.g. `char *envp[]`  
> **username_5**: Maybe the question is rather "why are modern languages wordier than C", and the answer is "because computers have a million times more RAM and disk space than 50 years ago, so we don't need to sacrifice readability any more to save memory".  
> **username_6**: C# still does this  
---
## 7 answers
### Answer by username_10 on 2024-09-09. Score: 64 | Accepted answer
The arguments and return code for main() are handled by the C implementation, not by your operating system. On some level, it's an arbitrary decision that C works this way and there is no particular reason why other languages should work this way.
Here is what your C implementation may actually be doing, more or less (not real code):
// _start is the ACTUAL entry point.
void _start(void) {
  int argc = get_argc();
  char *argv = get_argv();
  int status_code = main(argc, argv);
  exit(status_code);
}

How this actually works depends on the operating system, and every operating is different.
On Linux, the value of argc and argv are stored directly on the stack, so the entry point just has to read those values from the right location and then forward them to main(). You still have to call exit() after main returns.
On Windows, it's a little more complicated. The OS doesn't actually have argc and argv, it has a single string containing the command-line arguments. To get that string, you can call the function GetCommandLineA() or GetCommandLineW() (depending on which encoding you want to use). You can use this string directly, parse it yourself, or parse it using the standard functions CommandLineToArgvA() or CommandLineToArgvW(). So, Windows' version may look something like this:
void _start(void) {
  wchar_t *command_line = GetCommandLineW();
  int argc;
  wchar_t **argv = CommandLineToArgvW(command_line, &argc);
  int status_code = main(argc, argv);
  ExitProcess(status_code);
}

However, there is a pair of significant problems here:

On Windows, the command line does not include the program name. In other words, argv[0] will be missing.
On Windows, you may want the wchar_t version of the command line. (This is another reason why Rust has std::env::args() and std::env::args_os()--the args_os() version will let you work with wchar_t strings that cannot be encoded as UTF-8. This is a pathological case to be sure, but Rust is designed to give you the tools to handle pathological cases correctly.)

Conclusion
You can see that with all these differences between different operating systems, it doesn't really make sense to copy the way C does things. C doesn't do it very well.

In order for your process to exit, on common operating systems, you have to call a system call like _exit() or ExitProcess(). If main returns a status code, it just means that there is some code in your C runtime that takes the result of main() and calls that function.

The format for program arguments is different on different operating systems, and there is not a simple way to unify it all.
> **Comments:**
> **username_11** (2024-09-10): Great answer. I would just add that the C language was originally created for implementing utilities in the early Unix system. I see that as the reason why the Unix process calling interface is so integrated into C.  
> **username_12** (2024-09-10): I have to downvote as the answer is factually incorrect. On linux, you create a process using fork/exec. The exec variants actually do take an argc/argv; the process has an exit status you can query when calling "wait" variants. So the POSIX (i.e. OS standard) process creation API perfectly aligns with the C main API (by design). Why this fact emerges and whether it justifies this API for C programs is debatable (more probably bad design separation as stated in other answers). On windows the command line is indeed a single line, so the answer is more realistic/correct.  
> **username_10** (2024-09-10): @YannTM: There is no `argc` parameter to any of the `exec()` family functions.  
> **username_10** (2024-09-10): @YannTM: The POSIX standard describes the C interface. That C interface is provided by the C implementation on your operating system—it is not necessary to use the C interface when you are not using C.  
> **username_12** (2024-09-12): There is no argc, but the argv parameter in C is guaranteed to have a sentinel 0x0 last argument, so argc is actually not necessary, it's a feature. The args to exec are passed as is to main, so the point stands. The C interface is what the system implements, whether you acknowledge it or not, any process created on posix comes from this system call, you must indeed go through it to create a process.  
> **username_10** (2024-09-13): @YannTM: The C interface is provided by the crt, not the kernel. The only way that it is part of the "OS" is if you count crt as part of the OS--which is reasonable, since it's specified by POSIX, but whether you count crt as "part of the OS" is not really the question here. The fact is that other languages (1) run on systems besides Unix and/or (2) run on Unix but without using crt. Besides--most languages don't represent strings the way C does anyway, C is kind of a weird outlier.  
---
### Answer by username_16 on 2024-09-09. Score: 27
Different languages are useful for writing very different kinds of programs, in different domains entirely.

**Command-line interface might just not be the main way to interact with programs in a given language.**

As you point out, most or all "general purpose" languages do have a way to access argv/argc.

But they don't have to be **at the center of attention all the time**, especially if most programs are not expected to use them.

Also, many languages (like C# you mentioned) actually let you choose whether to use `int main(args)` or `void main()`.

---

(edit: adding examples from the comments)

When Java was originally released, it was popular for writing applets that run in the browser. When the program is an applet, the platform is the browser, and it doesn't expect a return value like a regular OS.

Even languages like Go, which seems to be geared for low-level programming and/or CLI/headless applications, can also be compiled to JS or wasm. Putting args and exit into a `os` package is a way to avoid **tightly coupling one specific entry point design** to the language. When designing a language it's usually a good thing to delegate concerns to libraries. When compiling for an environment without argv/exit, it's nicer to just have a missing `os` package than to have superfluous handling of fake empty arguments (from the perspective of both the language implementer and of the language user).
> **Comments:**
> **username_0** (2024-09-09): Surely it doesn't cost much to keep access to them more convenient? The OS speaks C's `int main(int argc, char **argv);`, the language just doesn't expose a similarly convenient form. Also, C# is the _only_ language that I know of that allows users to choose, and I only learned about it while researching this question - I've only ever known `void Main(string[] args);`. It's cousin Java, enforces `void main(String[] args);` for main.  
> **username_16** (2024-09-09): When Java was originally released, it was popular for writing applets that run in the browser. When the program is an applet, the platform is the browser, and it doesn't expect a return value like a regular OS.  
> **username_0** (2024-09-09): Fair enough, but then there's Rust and Go which seem to be geared for low-level programming and/or CLI/headless applications, and they don't expose the arguments or return value either.  
> **username_16** (2024-09-09): Rust and Go can also be compiled to JS. Putting args and exit into a `os` package is a way to avoid tightly coupling one specific entry point design to the language. When designing a language it's usually a good thing to delegate concerns to libraries. When compiling for an environment without argv/exit, it's nicer to just have a missing `os` package than to have superfluous handling of fake empty arguments (from the perspective of both the language implementer and of the language user).  
> **username_0** (2024-09-09): I assume by "compiling to JS", you mean targetting wasm? In which case, my quick test of compiling C to wasm with emscripten shows that wasm handles C's main exit codes and arguments correctly, but I guess I can see your point of some platforms not having the concept of exit codes and arguments to main.  
> **username_0** (2024-09-09): Maybe it's just me then, but I imagine "fake empty arguments" can be optimized away by the compiler -  I wouldn't mind having e.g., `argc` and `argv` be `0` and `NULL` or even UB to access if the platform didn't have those concepts, making it effectively free.  
> **username_17** (2024-09-09): @404NameNotFound: The reason for not passing dummies around is not performance, it's **semantics**. The human developer has enough concerns that they shouldn't need to, on top, wade through dummies.  
> **username_0** (2024-09-09): @MatthieuM. In that case, I think the C# approach is a good one - it's still there, but users can choose whether to use it or not. Maybe other languages think that's too complicated then?  
> **username_14** (2024-09-09): @404NameNotFound: The OS does not speak C's `int main(int argc, char** argv)`. That's a UNIX thing, which co-evolved with C. Windows uses `GetCommandLineW`, which doesn't include `argc`, is UTF-16 encoded, and retains whitespace. It's Visual C++ which wraps that up as `int main(int argc, char** argv)`  
> **username_18** (2024-09-11): @MSalters: One has to be a little careful with statements like that. On "most" Unixy platforms that are not Linux, libc is considered part of the OS (in the sense that libc is the official, API-stable interface for making syscalls, regardless of language, and OpenBSD goes one step further and actively tries to prevent syscalls from originating anywhere other than libc). It is true if by OS you mean "kernel," but that's a Linux-ism.  
---
### Answer by username_17 on 2024-09-09. Score: 26
*Disclaimer: I will not attempt to go spelunking into the archives of each language's design to figure out the reasons for why each of them decided NOT to follow in C's shoes. Instead, I'll focus on the downsides.*

Let's answer a slightly different question: **what's wrong with C's design?**

### Locality

From a user point of view, passing the command line arguments to `main` is mostly useful if those arguments are handled "close" to `main`.

If, instead, parsing the arguments involves a complex routine, and requires threading said arguments through multiple layers, then a user may prefer Python's or Rust's approach of being able to access said arguments from anywhere.

### Character Encoding

The problem of `char` is that... it's not a character. It's a byte. It can adequately represent ASCII characters, but anything further is a mess.

Unix uses... something close to UTF-8, so it works relatively well.

Windows uses something close to UTF-16 instead, and thus would need `wchar_t` instead of `char` for passing exactly the arguments passed.

And let's not even talk about embedded NUL bytes...

If you look closer at Rust you'll notice there are *two* APIs:

* `std::env::args()` returns UTF-8 strings, which is convenient, but not guaranteed to be able to map all arguments perfectly.
* `std::env::os_args()` returns `OsString`, which perfectly capture the OS-level argument, even on Windows, but may not be convertible to UTF-8.

Thankfully, both deal with 0.

### Performance

The signature of `main` in C forces *every* argument to be (1) converted to a `char` array and (2) stored in memory.

For example, if Rust used `fn main(argv: &'static [&'static str])`, then it would have to create an array of `&'static str`:

1. This would occupy `argv.len()` x 16 bytes (8 bytes length & 8 bytes pointer).
2. This would require validating that all arguments are, indeed, correct UTF-8 (which Unix doesn't guarantee).

If the user only ever checks the first argument, that's a lot of needless work, and needless memory used.

And of course, it gets worse on Windows, where the arguments are not stored in anything close to UTF-8 in the first place.

*Note: It's not unusual for C++ compilers, or linkers, to be invoked with hundreds to thousands of arguments.*

### Conclusion

C's design emerged over 40 years ago, in a very different environment.

Since then the world has evolved, and introduced challenges that the design copes... badly with. Band-aid upon band-aid to kinda make it work.

It's rather expected than 40 years later, with different requirements and the benefit of hindsight, new languages pick a different design.
> **Comments:**
> **username_19** (2024-09-10): "Unix uses... something close to UTF-8" -- I'm not sure what you mean with "something close", but I don't think too many unix-likes enforce UTF-8 or anything here, but just pass the bytes around. Except that there can't be embedded NUL-bytes, since API uses that as the string terminator.  
> **username_17** (2024-09-10): @ilkkachu: They don't enforce UTF-8 AFAIK, indeed, hence my specific choice of words (and phrasing).  
> **username_20** (2024-09-10): Re: memory, can you elaborate on how not using C’s design avoids keeping arguments in memory? If a process could request them at any time, that memory has to be allocated somewhere. (But maybe you just meant the extra memory of the pointers and arrays, not strings? If so, that point wasn’t clear to me.)  
> **username_17** (2024-09-10): @D.BenKnoble: In Rust, an iterator is created over the arguments, which creates (and yields) _one_ `String` (or `OsString`) at a time. Thus there's no permanent increase of memory, just an ephemeral one, one argument a time -- unless the user chooses to keep one of the `String` around, but then it's no longer overhead: it's a feature.  
> **username_20** (2024-09-10): Ok, so I think my parenthetical was the correct interpretation. Thanks.  
> **username_8** (2024-09-10): "*a user may prefer [the] approach of being able to access said arguments from anywhere*" - why is that considered a good thing here? Normally, global variables are considered an antipattern (due to creating implicit dependencies, and even worse if introducing mutable state). Not sure why `argv` should be an exception from this rule.  
> **username_21** (2024-09-11): Global variables aren't really a problem. The problem is shared mutable state. `argv` is not mutated (unless you are doing something incredibly sketchy) so it is not problematic.  
> **username_17** (2024-09-11): @Bergi: I did not say that being able to access argv from anywhere is good, I said users may prefer it. I don't consider it particularly good myself.  
> **username_17** (2024-09-11): @OscarSmith: You are correct that global mutable state is particular problematic, and global constants are not. `argv` is in the middle though: while it cannot be modified, its value is unknown until runtime -- which means you ideally ought to want to test with various values, but as you mentioned cannot really go and modify `argv` to do so. So it's not as clear cut, really.  
> **username_22** (2024-09-11): You first point, locality, is not "what's wrong with C's design?" but "what did C get right that all of these later languages insist on getting wrong?" Having the arguments be local to `main` ensures that nothing in the program (e.g. library code) can inspect and alter its behavior based on arguments without a chain of license to do so from the program entry point.  
> **username_17** (2024-09-12): @R..GitHubSTOPHELPINGICE: Bergi made the same argument. I already answered.  
> **username_22** (2024-09-16): @MatthieuM.: Bergi's remark is about something different, global state being bad vs library code hooking into and doing unwanted things with command line arguments being bad.  
---
### Answer by username_4 on 2024-09-09. Score: 11
### Even in the olden days, `main` and args varied.

Interpreted languages and functional languages often don't follow `main(char** argv)` style. Bash uses `$1 $2 $3` and executes everything in the outermost code block, like Python does. Perl uses `@ARGV` at global scope. Lisp/Scheme don't have a main, and don't have unified ways of getting argv. (Because why aren't you running them on a Lisp machine?) Fortran doesn't have a main fn, and argv access is added via functions like `getarg` and `get_command_argument`. Haskell's entry point takes only an IO monad, but provides `System.Environment.GetArgs`.
---
### Answer by username_13 on 2024-09-09. Score: 10
Most Programs are not Launched From Command Prompts
Those that are, are mainly running on Windows, Linux or UNIX, where other languages already fill the niche of command-line utilities.  Since this interface exists, you will occasionally see GUIs configure their shortcuts to pass options on the command line, if it might be invoked different ways depending on which one the user clicks, but even that is rare these days.
Modern languages normally provide some way to get the command-line arguments, but it’s more like how C programs look up environment variables.  It’s no longer assumed that every program is launched by an operator typing at a console or from a script saved as a text file. Command-line arguments are no longer so important that a general-purpose language would have special syntax sugar for them.
The argc and argv Data Structures are Terrible
I often find myself telling C programmers that it’s unfortunate everyone learns argv first, because a pointer to pointers to null-terminated strings is a terrible data structure and almost never the best kind of “two-dimensional array” to use.
Because they’re so old, they also let coders shoot themselves in the foot with a lot less warning than even modern C would give them, especially the type system not tracking the size of or preventing modification of any of the arguments or strings.
Modern languages all have some kind of native String type and some kind of generic Vector container, so a modern API would return the list either as a vector of strings or a vector of string views.
It’s an Awkward Fit for Type Systems
Languages sometimes allow function overloading, but they don’t normally have a feature that lets a function be called with different arguments depending on how it’s implemented in another file.  That’s not useful in real-world programs.  Even in C, written in part to be low-level enough to implement its own standard library (see “Why Pascal is not my Favorite Language”), there’s never been any demand for something like, hypothetically:
#if __extern_function_args(foo, int, char**)
    foo(argc, argv);
#elif __extern_function_args(foo, void)
    foo();

Even when ANSI C added variadic functions to make it possible to write printf() in C, they didn’t see any need to make it flexible enough to call either int foo(void) or int foo(int argc, char **argv), even in the same source file.  C and C++ make main() the one special exception to how every other function works.
There’s a lot of weird stuff in C because something happened, by coincidence, to work on the DEC PDP-11 back in its formative years and changing it now would break backward compatibility  There’s not much reason for other languages to do things the same way, other than to imitate C.
Some Newer Languages do Have Error Returns
For example, Rust allows main() to return any type that implements the std::process:Termination trait.  The Standard Library allows you to return an ErrorCode of SUCCESS or FAILURE, like EXIT_SUCCESS and EXIT_FAILURE in C.  But this feature really exists to allow the ? operator to work in short demo programs that do I/O (by short-circuiting on error and printing a debug message).
> **Comments:**
> **username_4** (2024-09-09): re: point 2, I don't think the issue is e.g. Java's argv being a String[] rather than a null-terminated char**. OP uses the example of writing a custom `cmain(argv: Vec<String>) -> i32` in Rust  
> **username_7** (2024-09-09): Another bit of weirdness is that the Standard didn't want to require that programs targeting platforms that don't recognize any concept of return codes must nonethelss end with `return 0;`, but didn't want such programs to malfunction on platforms that require that programs return zero to indicate success, so the Standard added a special rule for `main`'s return value.  
> **username_13** (2024-09-10): @supercat This is another one of those things that just happened to work on the DEC PDP-11 running UNIX. It’s also another way that `main()` is a precious snowflake. No other function declared as returning `int` is allowed to omit a `return` statement.  
> **username_14** (2024-09-10): "not preventing modification of any of the arguments " - why is that an issue? It's perfectly legal in C to modify your arguments. It is a true `char**`. There are reasonable designs which do modify their arguments as they're being parsed, to keep track of parsing state.  
> **username_15** (2024-09-10): For some reason, the essay [Why Pascal is not my favourite language](http://www.lysator.liu.se/c/bwk-on-pascal.html) is not as easy to find as one would expect, so here's a link.  
> **username_7** (2024-09-10): @TRiG: Many of the complaints in that essay relate to things that aren't problems in popular dialects of Pascal (and probably contribute to those dialects' popularity).  From a syntactic perspective, I like popular-dialect Pascal better than C except for the use of `;` as a statement separator rather than terminator and the lack of compound assignment operators which could have easily be incorporated into Pascal.  
> **username_13** (2024-09-10): @MSalters For example, K&R C made it very easy to write `if (argc = 4)` and clobber the length of `argv`, and didn’t even give you a way to code defensively against that kind of typo. (Modern compilers will at least warn you about this, unless you write double parenthesis to tell it you really meant to shoot yourself in the foot.) The language also lets you try to modify any of the pointers in `argv` or the strings it points to, but on many implementations, this breaks stuff. The only reason to allow this was that `const` didn’t exist yet when K&R established the convention.  
> **username_13** (2024-09-10): @MSalters Accidentally overwriting the terminating `\0` at the end of a string is even worse, because now an arbitrary number of bytes afterward become part of the string. Even modern C coding standards lean on the concept of vectors and slices so heavily, they’d bundle and abstract those data structures more than they did. And have some syntax for passing input parameters to a function by read-only reference.  
---
### Answer by username_9 on 2024-09-10. Score: 8
## "Explicit is better than implicit"

Philosophically for Python, giving an exit code back to the OS is doing something fundamentally different from passing a result from a called function back to the caller - especially since that code can only be a machine-level integer (of whatever platform-defined size), not a Python object. Indeed, `sys.exit` already needs to translate whatever's passed to it. Expecting a specific function to have the special behaviour of communicating back to the operating system, just because "it was called first", is a bridge too far. (Especially given that code actually starts running outside of any function - you could maybe argue that the top-level code "returns" a `module` object once it's done, but how do you translate that into success or failure, let alone allowing for multiple numbered failure status codes?)

## "Special cases aren't special enough to break the rules"

Similarly on the input side: the command line has to be parsed, or at least tokenized, and then *used somewhere*. But there's no specific function earmarked to receive them, because the general rule in Python is that functions are only called when you call them (another kind of explicitness). The only "automatic" running of code is from the top of the file downward - which is necessary e.g. so that `import`s can happen at runtime.

We might instead imagine the Python startup process assigning command-line arguments to a *builtin* name, which we could then treat as if it were a "parameter name" for the top-level code in the `__main__` module. But presumably we'd reject this idea on the same aesthetic grounds as already described. (After all, `sys` is a builtin module implemented directly by the interpreter rather than by Python code; one might equally well argue for dumping its entire contents into the builtin namespace preemptively.)
---
### Answer by username_7 on 2024-09-10. Score: 4
Unix was designed to minimize the amount of information a program launching another would need about the program being launched, and vice versa. The mechanism used when launching programs made it easy to pass a plurality of pointers to strings to a called program, and having `main` accept those as arguments avoided the need to have anything else in the "permanently loaded' operating system know or care about them. Programs that would need to access `argc` and `argv` outside of `main()` could have `main` store them to an external-scope object if desired.

Since that time, the use of general-purpose libraries became more widespread; some such libraries would need to access command-line arguments. If one such library would expect `argv` to have be stored into `char ***my_arg_values;` and another would expect it to have been stored into `char **my_arguments;`, then `main()` would have to ensure that `argv` was written to both such locations. Having to patch `main()` in order to use a library was rather a nuisance, so designers of later languages and systems established conventions for global-scope symbols for places where information about arguments would be stored at program invocation. Different languages and systems would use different symbol names, but a language designer could offer a consistent name for a function that could use whatever system-specific symbols or other means would be needed to retrieve the information in platform-independent fashion.

As for return value, it would have been apparent fairly early on that situations may arise where a program would discover in the middle of a nested function call that it wasn't going to be do anything useful. While it would have been possible to have `main()` use a couple of global symbols to perform a `setjmp()` and allow called code to store the return status before doing a `longjmp()` back to `main()`, which could then return the appropriate status code, this would require the called function to know what symbols the author of `main()` had chosen for that purpose. Having a call to `main()` wrapped in a platform/implementation-specific way which can then be exploited by a platform-specific implementation of a function with a standard `exit()` interface avoids the need for such coordination between the author of `main()` and code needing to exit.

Once the usable-from-anywhere means of accessing command-line arguments were standardized, the ability of `main()` to accept arguments directly and have its return value passed back to the system became redundant. While it made sense for C implementations to continue supporting them, there was no reason to carry that convention into other languages.
> **Comments:**
> **username_8** (2024-09-10): "*anything else in the "permanently loaded' operating system know or care about them*" - I have a feeling there's a word or two missing in that sentence, but I can't figure what it means either way. Are you referring to some sort of static memory allocation? In the program or in the operating system?  
> **username_7** (2024-09-10): Maybe you can offer better wording--the basic idea is that once the parent process had built up the argument strings and the pointers to them, and called `main()`, the parent process could be jetissoned from memory leaving behind nothing except those strings and pointers.  Having a pointer to the arguments live in a static-duration object would require that the calling program and called program both agree on where that object should be, and having a called program use a function to retrieve arguments would require spending precious RAM to hold that function.  
> **username_8** (2024-09-10): So are those strings and pointers living in the memory of the parent process, in the memory of the operating system, or in the memory of the child process? (Or is it all just shared memory?) Or is your point that it doesn't matter? I don't get what this has to do with the object lifetime though - surely no matter where they are placed, they must be held in memory until the child process ends (if it hasn't shared them further).  
> **username_7** (2024-09-11): @Bergi: I think the strings would have originally been placed at the top of what would become the stack of the new process.  In the first Unix system, each process would have almost the entire memory space of the computer available, save for a small amount reserved for the operating system.  Switching tasks would always mean storing the memory state of the current process to disk, and loading the memory state of the process to switch to from disk; there was no arbitration of memory usage between applications.  
---
