Be more precise about your goals? Typically you wouldn't mess with crate-type, wouldn't include the same modules in main.rs and lib.rs like you are with #[path], wouldn't use #[no_mangle]...
Cargo.toml
[package]
name = "whatever"
version = "0.1.0"
edition = "2024"
[dependencies]
You shlukd probably rename your main function inside the library. An unmangled function named main inside a dylib can't be called from an executable. Any attempts to call it from an executable will resolve to the main function of the executable instead irrespective of the language you write your program in. (technically you can make it resolve to the main function in the dylib on Windows and macOS using linker hacks, but that won't work on Linux as ELF simply doesn't have a way to refer to symbols in a specific dylib.)
In my opinion, what is causing the problem is that when importing the module from the library, then the main function should not stay an entrypoint, but switch to a regular function.
This way, you can import the main function and use it with another name, so no main is created from the lib.
#[path = "lib.rs"]
mod this;
use this::main as imported_main; // entrypoint deleted from this line
For as long as you are using #[no_mangle], it will override/conflict with the main function of the executable. Renaming imports don't have any effect on the symbol name.
Would our world be better if you tell that idea to Doctor Who who then uses TARDIS to change the past? Probably yes.
Is it good idea to go looking for TARDIS in your backyard in the hopes of doing that? Probably no.
The problem here is simply the fact that Rust doesn't exist in vacuum. Rules about how executables and dynamic libraries should behave were decided decades ago and changing them today would be a multi-decade long effort with billions of dollars spent on pushing new standards and new ideas about how things should work. Not something you may afford, I'm afraid. Except if you really know how to go into the past to change rules there.
P.S. What can, probably, be done, is to enable the ability to create binary without main… then main from the shared library would be found and used. I'm not sure that's good idea: I'm not sure how many developers even know this strange combo is even valid (in C/C++, not Rust) and it's unclear how many people would go mad when they would find program built like that.
The whole point of #[no_mangle] is to disable things that Rust does to “manage such a behavior” and allow the raw OS-provided mechanism to take over.