It's usage is almost equivalent to using IORefs, except we can escape ST using runST to get back a pure value not in ST, which we cannot do for IO because there is no `IO a -> a`.
There's no requirement to contain ST to a single function - we can split mutation over several functions, provided each one involved returns some `ST a` and their usage is combined with >>=.