I don't like global data/state and want to avoid it as much as possible. Therefore, the standard streams provided by the operating system will (indirectly) be passed to the main function. Ideally, the main function should accept just a single context parameter which gives platform/system-agnostic information and basic I/O stream instances. Then pattern matching (or something similar) can be used to get more specific information for the particular system. This parameter is an instance of a struct and can be passed around, copied, modified, or new instances of the struct can be created, etc.
Most modern systems support:
Standard input stream
Standard output stream
Standard error stream
Working directory
Path to this executable (or at least the name)
Command line arguments
Environment variables (common tropes like PATH should be accessible in a more agnostic view)
Number of CPUs (at least one, of course!), GPUs (0 or more), etc
Since the above are so common, they should have direct representations in the context struct without having to query for a specific OS/platform. If a particular platform doesn't support one or more of the above, Stardust should just fake it or ignore it (or do something useful as a substitute). This is a case where I believe exceptions shouldn't be thrown: the code shouldn't have to change its behavior for different systems if it doesn't want or need to.
There should also be a way of detecting and interfacing with a console, if it is present, but it should by no means be the default assumption.
Cross compiling
With cross compiling, code could run at compile time on one system and at runtime on an entirely different system. I'm not sure how to solve this conundrum.
Return value
Most systems expect a native integer return value, but the handling of said value is pretty inconsistent. Even C++ can't make up its mind: 0 and EXIT_SUCCESS are guaranteed to have the same meaning even though they don't have to be equal. Then EXIT_FAILURE is guaranteed to indicate failure, but what about other values that are not one of the two/three above? Unfortunately this is just the world we live in, so I think it's best to go with the C++ approach, with a twist: the main function should return a boolean, with true being success and false being failure, and there will be a dedicated function for requesting a specific return value (like std::exit).
Escaping exceptions
If an exception tries to escape main, in some languages this actually has behavior that is not possible with exit codes alone (I need to do research). I think it would be best to try and mimic that behavior for consistency's sake, but (unless the programmer asks otherwise) the exception backtrace should be printed to the standard error stream before exiting, if possible.
Dangling threads
If threads are still running when the main function ends, some languages mark this undefined behavior, or do other things. Maybe this is because some languages or systems treat the main thread as 'special' or 'different', but that seems like a weird implementation detail to me. I want to abstract that away: the main thread is a thread like any other. The program does not end until all threads end, main thread or not. Maybe the underlying implementation is that the main thread is the last no matter what, but that should be hidden from the user as much as possible.
Though typically it should be hard to arrive in such a situation: threads are held by scope and thus either terminate or block when they go out of scope (depending on what the programmer requested the behavior to be). The only way I can think of at this time for the main thread to exit before another thread is if that thread has an owning reference to itself (which may or may not be a good idea in some cases) or other such cyclic dependencies between threads exist, etc.
Either way, it is up to the programmer to ensure that their program is capable of fully exiting properly when needed.
I don't like global data/state and want to avoid it as much as possible. Therefore, the standard streams provided by the operating system will (indirectly) be passed to the main function. Ideally, the main function should accept just a single context parameter which gives platform/system-agnostic information and basic I/O stream instances. Then pattern matching (or something similar) can be used to get more specific information for the particular system. This parameter is an instance of a struct and can be passed around, copied, modified, or new instances of the struct can be created, etc.
Most modern systems support:
PATH
should be accessible in a more agnostic view)Since the above are so common, they should have direct representations in the context struct without having to query for a specific OS/platform. If a particular platform doesn't support one or more of the above, Stardust should just fake it or ignore it (or do something useful as a substitute). This is a case where I believe exceptions shouldn't be thrown: the code shouldn't have to change its behavior for different systems if it doesn't want or need to.
There should also be a way of detecting and interfacing with a console, if it is present, but it should by no means be the default assumption.
Cross compiling
With cross compiling, code could run at compile time on one system and at runtime on an entirely different system. I'm not sure how to solve this conundrum.
Return value
Most systems expect a native integer return value, but the handling of said value is pretty inconsistent. Even C++ can't make up its mind:
0
andEXIT_SUCCESS
are guaranteed to have the same meaning even though they don't have to be equal. ThenEXIT_FAILURE
is guaranteed to indicate failure, but what about other values that are not one of the two/three above? Unfortunately this is just the world we live in, so I think it's best to go with the C++ approach, with a twist: the main function should return a boolean, withtrue
being success andfalse
being failure, and there will be a dedicated function for requesting a specific return value (likestd::exit
).Escaping exceptions
If an exception tries to escape main, in some languages this actually has behavior that is not possible with exit codes alone (I need to do research). I think it would be best to try and mimic that behavior for consistency's sake, but (unless the programmer asks otherwise) the exception backtrace should be printed to the standard error stream before exiting, if possible.
Dangling threads
If threads are still running when the main function ends, some languages mark this undefined behavior, or do other things. Maybe this is because some languages or systems treat the main thread as 'special' or 'different', but that seems like a weird implementation detail to me. I want to abstract that away: the main thread is a thread like any other. The program does not end until all threads end, main thread or not. Maybe the underlying implementation is that the main thread is the last no matter what, but that should be hidden from the user as much as possible.
Though typically it should be hard to arrive in such a situation: threads are held by scope and thus either terminate or block when they go out of scope (depending on what the programmer requested the behavior to be). The only way I can think of at this time for the main thread to exit before another thread is if that thread has an owning reference to itself (which may or may not be a good idea in some cases) or other such cyclic dependencies between threads exist, etc.
Either way, it is up to the programmer to ensure that their program is capable of fully exiting properly when needed.