What you describe is called escape analysis, and it's Go's approach (for example). But it falls down in many situations, especially when separate compilation is desired or when you have existential types (e.g. closures). To truly have no GC with safety, you need to replicate something like Rust's machinery.
"If you use it correctly the compiler will figure out how to free it" is extremely handwavy. If you try to formalize exactly what "use it correctly" means, you will likely arrive at something very similar to Rust's system.
I think the idea is that with an effect system, function signatures and existential types will have effects that says "argument X does not escape", and "argument X can only escape through call Y" (so that you can infer bottom-up). If you have that information in the interface, you can do separate compilation and existential types.
To me this seems different enough to Rust's system, and while this won't be 100%, I think this will greatly reduce escape analysis failures, and especially this will eradicate escape analysis failures due to missed inlining. Whether it will be enough seems an open question to me (that is, to me, it doesn't seem destined to fail) and worth pursuing.
Once you're at the level of "argument X only escapes via closure Y", isn't that just a lifetime system? You're manually expressing the invariant that the closure Y cannot outlive argument X.
Comments
What you describe is called escape analysis, and it's Go's approach (for example). But it falls down in many situations, especially when separate compilation is desired or when you have existential types (e.g. closures). To truly have no GC with safety, you need to replicate something like Rust's machinery.
"If you use it correctly the compiler will figure out how to free it" is extremely handwavy. If you try to formalize exactly what "use it correctly" means, you will likely arrive at something very similar to Rust's system.
I think the idea is that with an effect system, function signatures and existential types will have effects that says "argument X does not escape", and "argument X can only escape through call Y" (so that you can infer bottom-up). If you have that information in the interface, you can do separate compilation and existential types.
To me this seems different enough to Rust's system, and while this won't be 100%, I think this will greatly reduce escape analysis failures, and especially this will eradicate escape analysis failures due to missed inlining. Whether it will be enough seems an open question to me (that is, to me, it doesn't seem destined to fail) and worth pursuing.
Once you're at the level of "argument X only escapes via closure Y", isn't that just a lifetime system? You're manually expressing the invariant that the closure Y cannot outlive argument X.
Would the cognitive overhead of that be any less than lifetimes in Rust?