|
1 | 1 | # Lifetimes |
2 | 2 |
|
3 | 3 | Emacs internals lifetimes. |
| 4 | + |
| 5 | +## Environment Lifetime |
| 6 | + |
| 7 | +Environment lives as long as the function it was given to executes. This covers both module initialization and Lisp functions defined in Swift. Using environment outside of its lifetime will most likely crash Emacs. You can think of ``Environment`` to be always an unowned reference. |
| 8 | + |
| 9 | +This means that storing ``Environment`` as part of some state, or capturing it in a closure that will be used afterwards is not going to work. The most typical problem looks somwehat like this: |
| 10 | +```swift |
| 11 | +try env.defun("test") { |
| 12 | + // some code before |
| 13 | + try env.funcall("lisp-function") |
| 14 | + // some code after |
| 15 | +} |
| 16 | +``` |
| 17 | +When the function does a lot of things, it's hard to spot the fact that we are actually using the wrong environment. We captured the one from the outer scope, and using it here will cause Emacs to crash. Instead, we can explicitly ask for a new instance of ``Environment`` in every Swift-defined Lisp function. |
| 18 | +```swift |
| 19 | +try env.defun("test") { |
| 20 | + (env: Environment) in |
| 21 | + // some code before |
| 22 | + try env.funcall("lisp-function") |
| 23 | + // some code after |
| 24 | +} |
| 25 | +``` |
| 26 | +This code doesn't change the number of required arguments to call `test`, Emacs already passes a new environemnt with every function invocation. This way we just ask ``Environment`` to pass it to us on call. |
| 27 | + |
| 28 | +This lifetime restriction ensures one of the core principles of Emacs dynamic modules **"Emacs calls into then module's code when it wants to, not the other way around"**. Using ``Environment`` outside of its lifetime means calling into Emacs asynchronously when Emacs does not expect it to happen. That will violate its concurrency model. |
| 29 | + |
| 30 | +## EmacsValue Lifetime |
| 31 | + |
| 32 | +Similarly to ``Environment``, opaque ``EmacsValue`` also has a limited lifetime. It is not enforced as strictly, and can produce even more confusion. |
| 33 | + |
| 34 | +Essentially every ``EmacsValue`` is bound to the ``Environment`` instance that produced it. This means that ``EmacsValue`` has *the same lifetime* as its ``Environment``. In the most probable scenario, you produce a value and use it with the same environment. Nothing to worry about in this case! |
| 35 | +```swift |
| 36 | +let value = try env.funcall("foo") |
| 37 | +try env.funcall("bar", with: value) |
| 38 | +``` |
| 39 | + |
| 40 | +The problem comes when you want to keep certain value and share it between two environments. |
| 41 | +```swift |
| 42 | +var stash: EmacsValue = env.Nil |
| 43 | +try env.defun("stash-arg") { |
| 44 | + (arg: EmacsValue) in stash = arg |
| 45 | +} |
| 46 | +try env.defun("get-stash") { |
| 47 | + stash |
| 48 | +} |
| 49 | +``` |
| 50 | +It should not work because of the lifetimes violation, but in most cases it does. On my machine, this code works as the user expected it to work. However, if we modify it a little bit, we can receive some very confusing results. |
| 51 | + |
| 52 | +```swift |
| 53 | +var stash = [EmacsValue]() |
| 54 | +try env.defun("stash-arg") { |
| 55 | + (arg: EmacsValue) in stash.append(arg) |
| 56 | +} |
| 57 | +try env.defun("get-stash") { |
| 58 | + stash |
| 59 | +} |
| 60 | +``` |
| 61 | +Looks very much the same, we keep all the arguments instead of the last one. So, what would be the problem? |
| 62 | +```emacs-lisp |
| 63 | +(stash-arg 1) |
| 64 | +(stash-arg 2) |
| 65 | +(stash-arg 3) |
| 66 | +(get-stash) ;; => [3 3 3] |
| 67 | +``` |
| 68 | +It returns a vector of `3`s! The size is right, the last value is right, but the whole vector shares the same value. It is especially strange after the previous example. The reason is the lifetime of `arg`, in this situation it is not enforced. However, Emacs reuses the same memory to store a new argument value every time. It is just an implementation detail of Emacs that we discovered accidentally. We should *never* rely on such undocumented features. They can change from one release to another, and behave differently on different platforms. |
| 69 | + |
| 70 | +Instead, we should use ``PersistentEmacsValue``. |
| 71 | +```swift |
| 72 | +var stash = [EmacsValue]() |
| 73 | +try env.defun("stash-arg") { |
| 74 | + (arg: PersistentEmacsValue) in stash.append(arg) |
| 75 | +} |
| 76 | +try env.defun("get-stash") { |
| 77 | + stash |
| 78 | +} |
| 79 | +``` |
| 80 | +This fixes it! We just changed parameter type of our function and that's enough! This way we tell `EmacsSwiftModule` that actually the Swift side should take care of this value's lifetime. ``PersistentEmacsValue`` effectively marries Swift's ARC and Emacs' garbage collection, so we can share values across environments. |
| 81 | + |
| 82 | +``PersistentEmacsValue`` also works with `funcall` and `apply` result type inferrence, so you can write: |
| 83 | +```swift |
| 84 | +let x: PersistentEmacsValue = try env.funcall("foo") |
| 85 | +let y = try env.funcall("bar") as PersistentEmacsValue |
| 86 | +``` |
| 87 | + |
| 88 | +If you already have ``EmacsValue``, you can turn it into ``PersistentEmacsValue`` by calling ``Environment/preserve(_:)``. |
| 89 | +```swift |
| 90 | +let lambda = try env.preserve(env.defun { |
| 91 | + // do some cool stuff |
| 92 | +}) |
| 93 | +``` |
| 94 | +After preservation, `lambda` can be safely used from different functions. |
| 95 | + |
| 96 | +> Info: ``Environment`` also provides ``Environment/retain(_:)`` and ``Environment/release(_:)`` low-level APIs for manual reference-counting. But ``PersistentEmacsValue`` and ``Environment/preserve(_:)`` should be preferred to avoid mistakes. |
| 97 | +
|
| 98 | +## Concurrency |
| 99 | + |
| 100 | +As it was mentioned earlier, ``Environment`` lifetime restriction comes from the desire to keep Emacs own concurrency model intact. This also includes another rule for using ``Environment`` - it should be used on the same thread it was created on. It is important to keep it mind, even considering that using ``Environment`` asynchronously will most likely violate its lifetime. To learn how to mix asynchronous code with Emacs interactions, please refer to <doc:AsyncCallbacks>. |
0 commit comments