3 ms·
>Rust is crying out for a way to treat structs generically (i.e. what frunk does with LabelledGeneric), but that only needs to be implemented once. There's no r
by Arnavion 7y ago
>Rust is crying out for a way to treat structs generically (i.e. what frunk does with LabelledGeneric), but that only needs to be implemented once. There's no reason serde and diesel couldn't be built on top of frunk [...]
What frunk does at runtime (well, its proc macros are still compile-time, but the code that makes use of the metadata is runtime), syn does at compile-time. I can understand that it's nice to have a simpler model where everything happens at runtime, but the things that proc macros can do include things that matter to the typesystem. For example, some of the macros I've written generate new types based on the input tts. You can't do that at runtime.
>And that's only going to get worse as more advanced tooling (IDEs, profilers etc.) becomes available and people rely more heavily on those tools to be deeply integrated with the language, because those tools are never going to be able to understand ad-hoc macros.
The mainstream tools (rls and rust-analyzer) get their information from the compiler, and thus have access to the expansion of the macro rather than just the macro invocation.
- lmm 7y ago> What frunk does at runtime (well, its proc macros are still compile-time, but the code that makes use of the metadata is runtime), syn does at compile-time. I can understand that it's nice to have a simpler model where everything happens at runtime, but the things that proc macros can do include things that matter to the typesystem. For example, some of the macros I've written generate new types based on the input tts. You can't do that at runtime. Again, though, that's something you want to be able to do in a standard first-class way in the language, rather than needing a custom macro. Depending on the use case you either want higher-kinded types (for transformations like producing a "patch" version for a given record, where all members are (recursively) optional) or dependent types (for transformations where you really just want to do a bunch of specific type->type cases). It's really not rocket science - Scala has been doing this stuff for years already. > The mainstream tools (rls and rust-analyzer) get their information from the compiler, and thus have access to the expansion of the macro rather than just the macro invocation. Sure, and that helps, but the tool can't possibly know what the full relation between input and output is (because by definition the macro is an arbitrary function), so it's never going to be as reliable as first-class code.
- Arnavion 7y ago>but the tool can't possibly know what the full relation between input and output is (because by definition the macro is an arbitrary function) The macro can associate output tokens with the spans of input tokens, either by reusing the input tokens (`#[derive(Foo)] struct S;` -> `impl Foo for S`, the `S` is reused), or using the macro API which allows specifying an arbitrary span for new tokens. This is required both for ident hygiene and for associating errors with the source code, thus it is a core part of the macro API. And since the compiler has this information, tools have it too. That means if you have #[foo] struct S; // (1) let s = S; // (2) and use an IDE to refactor-rename `S` to `S2` at the line marked (2), the IDE has enough information to know that it must also rename the `S` at the line marked (1), even though it's a macro input. This applies equally well to function-style proc macros `foo! { S }`