As a rough estimate? 10, but I don't know what you think that means.
If you look at what [changes] were introduced by the [2018] and [2021] editions, they weren't as earth shattering as some might think:
2018:
- Module system changes
- Mandatory associated fn argument names
- dyn, async, await and try are now keywords
- You can't write `let s = libc::getenv(k.as_ptr()) as const _;` anymore, instead needing `let s = libc::getenv(k.as_ptr()) as const libc::c_char;` (Method dispatch for raw pointers to inference variables)
2021:
- TryInto, TryFrom and IntoIterator added to the prelude
- cargo dependency resolver changes
- [1, 2, 3].into_iterator() now works
- `|| a.x + 1` now captures `a.x`, not `a`
- Small technically backwards incompatible change to the panic macro formatting string
- any_identifier#, any_identifier"...", and any_identifier'...' are now reserved syntax
- Some previously existing warnings are now errors
- a | b is now matched in pat macro rules
[changes]: https://doc.rust-lang.org/edition-guide
[2018]: https://doc.rust-lang.org/edition-guide/rust-2018/index.html
[2021]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
Edit: it just dawned on me that by "epoch" you might not have meant the edition mechanism, which was at some point referred to as epochs and have so far happened every three years, but rather "how many iterations of idiomatic Rust code will there be in 30 years". If that was the original intent, you would also consider things like the introduction of the ? operator, or the upcoming let pat = expr else {}, or match ergonomics, or the likely deref patterns, or any number of features that on isolation might not be huge, but that can materially impact what idiomatic code looks like. I personally believe that that kind of iteration and evolution of a language is good and necessary. As long as forwards compatibility is maintained, and that forward compat doesn't hinder the future design space, making things better over time is a great thing!