How to actually make a main function

Whilst trying to write a small HTTP server just for fun. I was trying to figure out how to use an actual main function. I am most of the time usualy just scripting, so I havent realy used the new @main syntax yet.

The following is my code:

module http_server_julia

using Sockets
using Logging

struct TCPServer
    host::String
    port::Int
end

TCPServer() =  TCPServer("127.0.0.1",8888)

#= function start(server::TCPServer)

    tcp_server = Sockets.listen(server.host, server.port)
    
    while True
    
        connection = Sockets.accept(tcp_server)
        @info "Connected by"  
    end
end =#


export main
(@main)(args) = println("Hello from the package!")

end # module http_server_julia

Ignore the fact that I havent figured out how to use the socket library yet. But no matter what i do, even just straigth up copying this from the main docs, I cannot get even a simple print to run. It just flat out does not work. I am loosing my mind right now. It can’t be that dificult to use the main function.

Have you tried adding

using .http_server_julia

at the very end of your file?

1 Like

ok yes that makes it work but why, and excuse my language here in all flying hells does this now work. Why is this required? WTF?

1 Like

this would have also worked if you removed the module. the reason is that @main needs to be at the top level, since program entrypoint is a global, not a local property. using the middle brings the main function into the global scope

2 Likes

In other words the command-line Julia runs Main.main(ARGS) automatically if you use @main to opt-in to it. So you need to define this function either inside the Main module directly or bring it into Main from another module using .MyModule or other. Command-line Interface · The Julia Language

1 Like

I never bother with main. I just put the stuff into a “top” function and I call it at the bottom of the script.

5 Likes

Ah that makes sense to me now. Does that also work in the case of having multiple files in a package? Or how does one show the Julia Compiler which file has the entry point?

Let’s look at what an entry point or main function is. In the most straightforward definition, this is where your program starts execution after loading the necessary code. In many approaches, you denote the start (and end) of the program in a function with a specific signature and name, often “main” or something similar. Your program can’t start in several places at once, so there’s an ahead-of-time (AOT) compiler that looks at all of your code and complains if it finds multiple main functions. This does apply if you were compiling PackageCompiler.jl apps (julia_main()::Cint) or the upcoming juliac executables (Base.@ccallable main()::Cint, the last time I read about it).

When you run the julia command, you’re not AOT-compiling the input code, regardless of whether you’re entering an interactive REPL session or going back to the command line afterward. With the exception of what packages installed in a given environment opted to precompile, Julia parses and executes expressions one by one and compiles native code just-in-time (JIT), on demand. Your program started at the first line and ended at the last line like any of your scripts; the last thing it did was define a (@main)(args) method you never call and wrap up the module. (Technically (@main) expands to the simple function name main, but the macro call also does a critical registration step in the background, so I’ll keep referring to the definition by the macro call). If you just needed to run a program from the command line, you didn’t need the enclosing module, you didn’t need a (@main) function, and you didn’t need to wrap your primary work in one function to call (though it’s much easier for the JIT compiler to optimize a function call, so it’s often recommended).

So if (@main) isn’t where the Julia program begins, what is it? Its docstring refers to it as an entry point for the current module, not one for the whole program. The command line interface documentation documents the -m/--module flag as running the (@main) entry point for a package (all packages are modules hence the flag’s name). Each module can have its own (@main) to implement an application routine of sorts in addition to the usual library routines, and it can run automatically from the command line with the -m/--module flag or with a unique import of main to the Main module that all Julia programs start in (which is what the -m flag does for packages). Main.main(ARGS) executes when the process is about to automatically exit, so this arguably isn’t an “entry point” at all, but that’s the term we have to live with.

This is and likely will remain a very rarely used feature because it saves negligible effort compared to any explicit function call, including hppt_server_julia.main(ARGS). The only way to entirely avoid an explicit function call or import statement in Main is the -m flag or defining (@main) right in Main of the input file or expression at the command line, and that import statement can otherwise become about as long because packages on principle won’t export a common or conventional name like main via bare using statements. It is really only convenient for the -m flag, but I’ve never ran across a package to use it for and it doesn’t even seem available for me (v1.11.5, Windows).

You might be going back to this question at this point because your script only has the one (@main), so shouldn’t Julia be able to automatically identify this and run it for you, even if the program doesn’t really start there? At least 2 problems: 1) Julia doesn’t have an AOT compiler by default to even try to identify and enforce one (@main) definition, and 2) multiple coexisting modules allow multiple (@main) by design. If you import a package with a (@main) before your own, then include another module file with a (@main), there’s no good reason to automatically call your (@main) instead of the others. A definition or import into Main is crucial for stable selection.

1 Like