If you can design your schema and usage patterns so as not to hit those limits, it's a very scalable option. But in return, you are in for a very strong vendor lockin.
Unfortunately, almost all pay as you go, linearly scalable managed databases, for example, FireStore, DynamoDB, CosmosDB are essentially lockin in nature (and maybe by design)!
Other OSS NoSQL products almost always need a base cluster costing $1000 (or more) per month to get decent baseline performance. After that point, you can scale to a very high scale by vertically and horizontally scaling your cluster.
C++?
Neither is Rust, most of the things that seem weird or new in Rust already exist in functional languages.
It has developed a lot of idiosyncrasies that range from "confusing unique syntax" to "using these two features together will blow your app up with no warning from the compiler".
Structs and enums are all over the place
Free function definitions, and methods defined on a data type, are extremely common
Rust Traits are ways of naming groups of operations to be associated with different types, and share a lot of similarity with Type classes, roles, interfaces, mixins, etc. present in many common languages
async/await is similarly present in many common languages
Generics / Parametric Polymorphism, extremely familiar from C++, Java, C#, etc.
It's almost all modern takes on normal common PL things that have been in many languages for decades
So by my perspective, I see a very normal imperative language, with its own take and small quirks on very familiar features, and the only feature I've been able to come up with that's "completely unlike every other language people have been using for decades" is the borrow checker. One new unique feature seems well within the weirdness budget of a new language.
Could you expand a bit more on what it looks like from your perspective? What other features or properties of the language seem strange, alien, foreign, or otherwise completely unfamiliar to you?
I agree that Rust has some things (maybe just one thing: the ownership + borrow checking system) that are "completely unlike every other language people have been using for decades", but so does C++.
Even some simple sharing can be a pain and counter intrutive.
I would add that tooling and stability are also not a thing for Rust as of now, that being said it's pretty clear that Rust is here to stay like Go.
That being said, those structures are a subset of all possible structures and may feel roundabout. Sharing does end up being even less obvious, but it also steers you away from bugs which would be difficult to spot in other laguages. I think it comes out even here.
There are little conveniences in Rust like switch / if being expressions and the ? operator, but the gimped Templates and refusal to do Implicit Conversion wind up littering boilerplate everywhere. I do somewhat prefer the way its standard library handles iterators, though.
let v: Vec<u8> = b"3q2+7w==".iter().cloned().collect();
and let v: HashSet<u8> = b"3q2+7w==".iter().cloned().collect();
are both valid, even though both statements have the same expression on the right hand side. In a language like Java you could assign a value to different types based on a sub-type / super-type relationship (Object foo = Arrays.asList(...)), but that's not the case here, nor is it the case that Vec<u8> and HashSet<u8> have the same layout in memory. Instead, according to the thread, the result from collect can be interpreted as any type that implements the FromIterator trait.I don't know what the name for this sort of behavior is, and I haven't seen it in other languages. Perhaps it's just a property of Rust's trait system? Regardless, it feels intuitive to me - like the compiler is taking type information and using it to make implicit casts throughout the program.
Not a huge impediment - just something I found confusing that isn't related to the borrow checker.
[0] https://users.rust-lang.org/t/why-cant-the-compiler-the-type...
What's throwing him off is static method dispatch based on type inference on the return value.
Haskell, for example, does this too:
class Foo a where foo :: a
instance Foo Integer where foo = 3
instance Foo Char where foo = '*'
foo1 :: Integer
foo1 = foo
foo2 :: Char
foo2 = foo
main = do
print foo1 -- prints: 3
print foo2 -- prints: '*'It's not a cast. Rather, it's an inferred/omitted type parameter. In this case, the collect() method is actually collect::<ContainerType>(). So it's not that collect returns something to be cast, but rather that there is a different collect() implementation for each target type. When the compiler is able to infer the target type unambiguously -- for example here, because you assign it to a typed variable -- then you don't have to specify it.