You can create data structures with cycles without explicit references, using lazy types or functions. Indeed, both of them hides some form of mutability.
Here is an example of a simplest lazy structure, that is more complex than a list
type 'a tree = 'a tr Lazy.t
and 'a tr = Stem of 'a * 'a tree * 'a tree
let rec tree_with_loop : int tree =
lazy (Stem (42,tree_with_loop,tree_with_loop))
But, you should understand, that with this kind of structures (i.e., those that contains cycles) you're stepping to an infirm ground of infinity, as all your traversing functions now diverge.
And here is the same example, but without lazy:
type 'a tree = unit -> 'a tr
and 'a tr = Stem of 'a * 'a tree * 'a tree
let rec tree_with_loop : int tree =
fun () -> Stem (42,tree_with_loop,tree_with_loop)
And here is an example of a slightly less infinite tree:
type 'a tree = 'a tr Lazy.t
and 'a tr =
| Node of 'a
| Tree of 'a tree * 'a tree
let rec tree_with_loop : int tree =
lazy (Tree (tree_with_loop,
lazy (Node 42)))