Thank you for this example. Would it help if one of the copy operations is a no-op? For clarity, let's call the two copy operations (one in each direction) transient->persistent and persistent->transient. The latter procedure creates a genuine copy. The semantics of the former, however, is that it invalidates its argument (because it is acting on a transient structure). This means that it can become a no-op because modifying its input afterward would be an error because it is seen as being invalidated.
So, the library API accepts persistent input, and it uses persistent->transient on input if it wants to take advantage of linear-updating updaters in it:
(define (my-library-function input)
(... (update! (persistent->transient input))))
And the caller calls transient->persistent if it wants to pass transient object to my-library-function:
(my-library-function (transient->persistent (update! object)))
If the library wants to provide faster, non-copying version, it can expose transient-accepting linear-updater as well:
(define (my-library-function! input)
(... (update! input)))
Yeah, this is clean. A strictly-checking implementation can raise an error seeing persistent/transient flag in object, if it desires. Loosely-checking implementation just use copy for transient->persistent and identity for persistent->transient.
Am I understanding it correctly?