dyn.txt 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. The dynamic *environment* consists of a set of 'dyn' variables.
  2. 'dyn' variables are declared at crate-level.
  3. dyn variables can be 'set' or 'not-set'.
  4. dyn_set[T](d) is a predicate.
  5. loading from a dyn variable requires the dyn_set(d) predicate
  6. in the current typestate.
  7. storing directly to (or through) a dyn is illegal. a dyn is
  8. implicitly a read-alias slot. dyns can only be "set" via a
  9. braced-construct that sets-on-entry and unsets-on-exit: bind.
  10. bind (sys.stdin = null_chan();
  11. sys.stderr = out1;
  12. sys.stdout = out1) {
  13. ... some computation ...
  14. }
  15. bind? or with? hmm
  16. with (sys.stdin = null_chan();
  17. sys.stderr = out1;
  18. sys.stdout = out1) {
  19. ... some computation ...
  20. }
  21. What are dyns good for?
  22. - Arguments that are so ubiquitous that you would have to thread them
  23. through every call in the system to get anything to work.
  24. - Working around brittle interfaces you can't necessarily change but you
  25. need to pass a few more parameters through: put them in the dynamic
  26. environment, pull them out inside, fail if you can't. ***NOTE*** actually
  27. this is a bogus reason, see below.
  28. - does this really make sense? why not just use a closure? what's the
  29. example 'interface' through which you can't just close over the
  30. extra arguments? it'd be pretty artificial: some kind of combiner
  31. that composes 2 user-provided functions f1 and f2 into f1(wrapper(f2));
  32. you'd then receive a wrapper as f1 and if you wanted to communicate
  33. with f2 you'd be sunk using closures because you and f2 don't share
  34. variables environment. Only so sunk though. You could spawn a proc
  35. outside and give f1 the port and f2 the chan, and have the
  36. communication go that way. Awkward, but I think this is an artificial
  37. argument.
  38. - should dyns be used *in place* of closures? they serve different
  39. purposes. closures bundle an environment into a function. doing
  40. so would be equivalent to only allowing "downward" closures: callees
  41. can see the Nth ancestral call environment, but you can't return
  42. a closure capturing a copy of that environment. so ... possibly
  43. could weaken closures to dyns; but probably this will annoy people.
  44. on the other hand, the idioms of using closures to "encapsulate
  45. mutable state" possibly won't work in rust *anyways*, as the envs
  46. captured in closures cannot be allowed to share mutable-visibility
  47. bindings. final argument in the coffin perhaps: dyns would necessarily
  48. require re-checking the environment for a binding if they were used
  49. in place of downward closures, because you'd be using them to pass
  50. args through an ignorant intermediary fn that doesn't properly
  51. set the typestate. This would make them awful and unwieldy. Also it'd
  52. require tracking dyn tables at the level of scopes (possibly with
  53. closures themselves) rather than at the level of static crate entries.
  54. So no. Closures != dyns.
  55. - Anything you might use a file descriptor number or an environment
  56. variable for in C/unix.
  57. - Making environment-requirements *known*. The dyn_set(d) predicate can
  58. be *exported* in your function's signature if you like, as with any
  59. other typestate. This makes it possible to encode (in the public
  60. entry-points) that a whole subsystem needs, say, stdio setup, or a
  61. database connection or such, without having to *pass* stdio in and out
  62. of everything inside the subsystem. They can check() for it as needed.
  63. What are dyns not?
  64. - They are not global mutable variables. They can only be used to pass
  65. information *down* the dynamic environment.
  66. - You cannot race on them. They are per-proc. There is a spawn variant
  67. that copies the dyn environment of the current proc, and one that does
  68. not.
  69. - They are not pure. Accessing a dyn makes a function non-pure. It's an
  70. extra implicit argument that can't be statically associated with a
  71. particular slot. What winds up in the dyn at runtime may be anything.
  72. - They are not a way of working around a dedicated effort at
  73. encapsulation. If you are a callee, an intermediate caller can clear
  74. the dynamic environment you get (via reflection), and/or corrupt your
  75. dyn settings, just as easily as they can honor them.
  76. Spawning in a dyn environment?
  77. Clearly the child process' dyn environment differs from the parent's. It has its own types of
  78. functions and should be isolated from covert channels anyways. I think? Though I suppose in theory
  79. you could have the program type have dyn(x) constraints and spawn copies the declared portion of the
  80. environment from spawner to spawnee? that'd require the dyn(x=y) {..} construct to occur in
  81. expression context though, which ... might already be necessary?
  82. no, it's not. we're going to have the stmt-as-expr form, so we can just make a dyn stmt form.
  83. dyn { ... } means execute ... in a blank environment
  84. dyn (x=y, p=q) { ... } means execute ... in an environment with only an x=y and p=q binding
  85. dyn+ (x=y) { ... } means execute ... in an environment extended with an x=y binding
  86. dyn- (x,y,z) { ... } means execute ... in an environment with the x,y,z bindings unset
  87. This is probably overkill. It's not clear that there are important idioms that would use
  88. this. In fact, given that channels vs. ports are *half duplex* anyways, it's not clear to me
  89. that the advantage of tersely saying:
  90. prog needs_stdio {
  91. port[buf] in;
  92. init(vec[str] argv) : dyn(sys.stdout), dyn(sys.stderr) -> chan[buf]
  93. {
  94. in = port();
  95. ret chan(port);
  96. }
  97. }
  98. over more verbosely saying:
  99. type buf = vec[u8];
  100. prog needs_stdio {
  101. port[buf] in;
  102. chan[buf] out;
  103. chan[buf] err;
  104. init(vec[str] argv, chan[buf] _out, chan[buf] _err) -> chan[buf]
  105. {
  106. out = _out;
  107. err = _err;
  108. in = port();
  109. ret chan(port);
  110. }
  111. }
  112. when you consider that the idiomatic call-side carries some of the (minor) difference
  113. in typing-burden:
  114. type buf = vec[u8];
  115. let (port[buf] out, port[buf] err) = (port(), port());
  116. let chan[buf] in = spawn (sys.stdout=chan(out),
  117. sys.stderr=chan(err)) needs_stdio(args);
  118. vs. w/o any dyn envs:
  119. type buf = vec[u8];
  120. let (port[buf] out, port[buf] err) = (port(), port());
  121. let chan[buf] in = spawn needs_stdio(args, chan(out), chan(err));
  122. I think for the sake of simplicity I will actually leave dyn envs out of the first pass
  123. entirely. we'll see if we eventually need them -- say for error-handler binding or something -- but
  124. I think they may be over-engineering.