;;;; Two rendered numbers, held at once. ;;;; ;;;; i64->bytes and its two siblings render into a buffer and answer a slice ;;;; over it. That buffer used to be one file-static in the runtime, shared by ;;;; every call in the process, so the program below printed "22 22": the ;;;; second conversion overwrote the first, and the first slice — still a ;;;; perfectly valid pointer into a perfectly live buffer — was read after it. ;;;; No crash, no diagnostic, and nothing for a sanitizer to catch, because ;;;; every byte read was inside an object that was alive. The wrong bytes. ;;;; ;;;; The buffer is the caller's now, one frame slot per call site, which is why ;;;; the two conversions below do not collide and why the f64 held across an ;;;; i64 conversion — a different shim, and the same buffer before — survives ;;;; it. What the slice still does not outlive is its frame: storing one in a ;;;; container that lives longer, or returning it, hands back a view of storage ;;;; that has been reused. That is copying's job and is said in check.ml. (defn main [] i32 ;; Two i64 conversions alive at the same time. (let [a (string (i64->bytes 11)) b (string (i64->bytes 22))] (print a) (print " ") (println b)) ; 11 22 ;; Three, and read in the order they were made rather than in reverse, so a ;; version that rotated among two buffers would still be caught. (let [a (string (i64->bytes 1)) b (string (i64->bytes 2)) c (string (i64->bytes 3))] (print a) (print b) (println c)) ; 123 ;; Across the two shims: the f64's text is made first and read last. (let [x (string (f64->bytes 2.5)) n (string (i64->bytes 7))] (print x) (print " ") (println n)) ; 2.5 7 ;; Inside a loop, where the slot is reused per iteration: each turn's text is ;; read before the next turn writes it, which is the contract a frame slot ;; gives. Printed on one line so the loop's shape is visible in the output. (dotimes [i 4] (let [s (string (i64->bytes (i64 (* i 11))))] (print s) (print " "))) (println "") ; 0 11 22 33 0)