I’ve been following the Mojo project since its inception, and I was surprised by the recent proposal to wind down fn and unify around def. While I completely understand the motivation—the significant feature overlap between the two was definitely confusing—I feel like this deprecation raises a major problem with Mojo’s goal of being a Python superset.
We’ve always known Mojo wouldn’t be a strict superset, so introducing new keywords is fine. However, the decision that def will no longer implicitly raise means a vast universe of existing Python functions will simply fail to compile. Any standard Python def with an error path will now require manual modification to def ... raises.
Doesn’t this create a massive friction point for Python compatibility? I’d love to hear your thoughts.
Footnote: It’s surprising since to look back at this thread from May 2025, where the core team firmly defended keeping def strictly as the “Python compatibility bucket” while keeping fn separate. Breaking standard Python def behavior now feels like a shift away from that original compatibility view.
At this point, that goal is mostly aspirational for a point far in the future. There are a lot of really, really nasty problems we’ve run into, such as soundness around trying to determine whether a python object implements a trait, and I’m not sure if it’s actually possible to merge Python and a statically typed language. I think other people are coming to the same conclusion. There are some options for doing whole program type inference for python-like code, in a similar way to what typescript can do, but they will massively blow up compile times and are likely to have issues with C extensions. However, at this point myself and others see that as the only way forwards to handle much more than the subset of python written by people who use mypy strict without any exceptions for their dependencies.
For example, consider the following python:
a = [1, 2, 3, mylib.ThreadUnsafeCExtensionType()]
How do we determine that ThreadUnsafeCExtensionType is thread unsafe in the general case? Alternatively, how do we determine when types are thread safe? You’d need to do some fairly aggressively analysis of the C code backing it, which isn’t always possible.
Additionally, at this point, when people hear “python compatible”, they think “CPython compatible”, which is an even harder task. If Mojo were to implement every Python PEP exactly, it’s likely it still would not be considered “a real python superset” by most people, because import numpy might not work unless we also clone CPython’s extension API, which isn’t standardized. If you also implement that, you’ve basically just reimplemented CPython, at which point it would have been better to just call out to cpython. For now, we encourage you to do incremental porting since Mojo makes it easy to expose Mojo as a python module, and I personally have found that sufficient for porting hot loops to Mojo, which gets a lot of the benefits of Mojo while still keeping the developer experience for my coworkers.
I don’t think that we do, and it would probably be useful to have one. Especially since people keep repeating “superset of python” after Mojo has tried to distance itself from that. @Caroline do you have any thoughts on this?
But then why keep def, instead of fn? At least it would look different from Python, thus avoiding confusion. Also it’s probably easier to convert Python code to Mojo code if you have to replace def with fn … raises .AI models would probably also have a difficult time with def, because they’ve been trained on so much Python code.
When there is a fork in the syntax road, Mojo tends to take the Python branch of the fork as the default position. If there’s a strong reason not to, it does break with Python. I don’t remember where that was explained, but it was/is a mechanism to move the language quickly instead of getting stuck on syntax design points.