Concerning macro declaration order: you can write `use ;` to "elevate" the macro into a proper symbol that behaves like everything else (inlcuding being accessible by full module path etc). It's a weird little quirk about declarative macros in Rust.
You probably already know it, but there is a library "clap" that will derive CLI interface from structs using #[derive] allowing you to have declarative style of defining your cli api
@@igorfedenko8212 Reinventing the wheel is not optimal when dealing with constraints or production code... this however is recreational programming ...the whole point of this is to learn something while having fun and diving under the hood! :)
I very much appreciated the "motivating example" at the beginning. It's extremely helpful to see what the "naive" way of doing something looks like so that the alternative way of doing the same thing makes more sense.
I was searching for someone to explain Rust Macro's. Didn't really felt like there was someone with a good example or explain video (at least not for me). Now just watching this video because I like how you do things and explain it simple but clear, never thought you will just do the thing I was searching for/was stuck on just because you wanted to use it for yourself. Thank you
Just WOW. I'm programming my entire renderer, engine and game in Rust since 1.5 years ago and I'm not even at 10% of this level of efficiency and knowledge. The bell curve meme is so real.
And I think that's the reason Actix, Tower and other Rust libraries don't auto register services and controllers. Thanks for the video. I really like it.
Btw about exitcode, quite sure that when main returns a Result, the Err variant expands to 1 (which is trivial to impl by interpreting the enum as an numeric type by position, Ok variant being 0 (success) and Err variant being 1 (i.e exit code 0/1)). From my understanding of what you were doing it's probably simple to just make main return a Result and so Err would automatically return "exit code" 1, and then you could add info to the Err itself (instead or alongside the eprintln()). Edit: obviously you're then limited to 0/1 which isn't necessarily always what you would want.
44:00 you could've just done this struct Command where T: Iterator, { name: &'static str, description: &'static str, run: fn(&str, T) -> ExitCode, } the reason why impl Iterator is not allowed in structs is because it hides the type. structs need explicit generics to reveal the whole type. functions don't need that because the type information is revealed in both cases fn foo1(a: impl Iterator) {} fn foo2(a: T) where T: Iterator {} Foo Foo foo1(SomeType1) foo2(SomeType2) without generics in struct, you would instantiate two structs of different types that look the same let f1: Foo = Foo { run: MyType1 } let f2: Foo = Foo { run: MyType2 } both f1 and f2 are of type Foo instead of Foo and Foo
Java is not the greatest language but for some usecases it makes things very convenient. With reflection you can get a list of all classes that implement an interface and turn that into a list of commands with a single function call. Something similar should be possible in other languages, but Java is where I have implemented it myself.
Rust macros really are another thing, most of the times you just need to do some basic processing/generation with the metadata, not some real dynamic thing at runtime, you just need to query the program structure at compiler time. And you don't need to care about things like performance of reflection or security problems and reflection is a PITA to use because it literally turns your static language into a dynamic one. I really liked the Rust solution for this problem. Also, F# has a cool thing with TypeProviders that allow something similar, specially when you target the TypeProvider to a Type defined in the software. TypeProviders basically allow you to direct input any structured data as Types directly to the compiler itself, you basically compile an DLL/Assembly that loads inside the compiler and thus you can provide types for the compiler, its much more advanced than reflection, and simpler to use, almost like T4 in C#, but better and typed and you don't need to do something akin to "PHP", preprocessing like its "1990 C".
A potential fun way to avoid the dependency on macro run order. (Note: I'm only at 1:25:00, so not sure if something else has come up later int he video) Instead of tracking command state in the compiler plugin, you could track the state in a custom section of the elf and collect the arguments at runtime. The linux kernel does something similar for module init where it puts function pointers in an init section of the kernel elf and iterates the section at runtime. You could do something similar where you take your command struct, and essentially pack those structs into a special section of the binary at compile/link time. Then all you have to do to get the commands back is to iterate that special section as the first thing you do in main. The gcc equivalent would be to use __attribute__((section(...))) I've done this a few times (never in rust though), and it's the only way I've managed to think of to get a compile time registry that doesn't depend on order of compilation
Doing some very quick googling, it seems like you could define static variables with #[link_section] and that would probably give the desired results. Maybe with some alignment constraints to think about
You can do the trick with sections, but you have to modify your linker script which is a pain, and I'm not sure even possible on windows, so it won't be cross platform.
You can also collect the commands at runtime if you register them via ".init" section, but once again it's super unsafe because it runs before main and once again it's not cross platform.
That sounds like such a pain that full-on meta-programming is preferable. Just output the source code you need. Probably much more understandable as well...
I don’t know much about Rust but I think the reason a prod macro has to be its own crate type is because it’s actually changing the way that the compiler itself processes the input. I’d guess that rustc actually loads the dynamic library that the proc-macro crate generates for its own use.
For making `command_list!()` execute last, you would probably need an extra crate that depends on all other crates that declare any Commands. Then, you can guarantee `command_list!()` is called after all the definition proc macros executed. Annoying, but I can't think of any other way with the current tools rust gives you. In theory adding support in rustc for something like an `#[order(last)]` annotation for proc-macro crates should be relatively straightforward to support, I think. Anyway you can now do a way shorter video about how it would be way simpler in zig/jai. At the very least it's good market pressure.
True. Although the rustier way to do it would probably be to group every function inside an impl and use an attribute macro on the whole thing. More parsing but no dependency to undefined compiler behavior.
I am trying to build my own command line application, even as a beginner I really love ur videos, they are easy enough for me to understand and I love how unique ur takes are sometimes XD
There is a crate called inventory, you can take a look into how it works to see how not to depend on the macros being called in order of their appearance in the code
Except that how inventory works is extremely technical with different implementations for different operating systems and it uses the link_section attribute. In other words, if you want to do it the way that inventory does it, then it's best to just use inventory.
The way I normally approach this is by having a command line parser struct containing the map along with methods like add_flag or add_positional_argument that takes the the name, description and a lambda which binds the string argument to a variable captured by reference. Then after having added all commands, I call parser.parse(argc, argv). For adding subcommands you'd probably want a subcommand_start and subcommand_end as well though.
This reminds me of the time I did something similarly cursed to python type introspection to create argument parsers just from types. The library is called arcparse for anyone interested.
@58:00 I suspect that it has something to do with the fact that proc macros are effectively compiler extensions, though I dunno what additional requirements this would place on the crate over being a normal dynamic library. It might be that they explicitly don't want it possible to include both normal code and macros from the same library, so when compiling you have to say "this is a normal library" or "this is a proc macro crate and if you want to use it for something else, it's not sanctioned by the compiler". Proc macros are DLL because they behave like an other Rust and because in large projects with complicated derive macros, they need to be fast. Which means you want them to be compiled. But they serve effectively as compiler extensions, so unless you want to recompile the entire compiler with your macro crate statically linked in, they need to be a DLL which the compiler can dynamically link against. @1:04:00 The reason it's a token tree and not a string or an AST, based on documentation I've read is that - it's a more consistent thing for the compiler to target than an AST (i.e., if additional language features add new syntax, the AST is going to have to change and this might break outstanding macros, but it'd take a damn radical change to change how the compiler tokenizes things). They're confident that matching parenthesis/square brackets/curly braces are always going to matched, but this leaves the details free to change without breaking macros. Additionally, Rust proc-macros are meant to not be limited to taking in Rust syntax and spitting out Rust syntax; they're designed to be able to take in, e.g., a DSL and spit out Rust syntax, and so while "parenthesis, etc., need to be balanced" is a reasonable limitation, they don't actually want to require that the input actually parse as valid Rust. - it's much easier to work with than a raw string @1:11:10 `for _ in _.into_iter()` is always redundant because the argument to a for loop must implement into iterator and into iterator is invoked on it, rather than implementing iterator. @1:25:00 I think the reason to do it that way is just "able to express more things." Since you want them to be able to support DSLs which have different rules, better to have this be two tokens rather than one.
2:00:00 I think rust will have to gurantee the order at least per single source file. (The only other method than using your macro list would be even worse - injection in the tree)
actually makes sense, that it isn't a proper ast, since you want to do stuff like compile time checked sql which obviously isn't in rust syntax. at least that's what i assume why? lol Edit: really wonder if there is a way to keep state in a proc macro that would be "rust approved" ^^
I think I know precisely what's going to happen. (when I saw that .so) Rust is going to load that inside the compiler itself, the compiler is going to do dlopen
That was some gnarly code. The fact that the compiler allows for plugins like that is kind of horrific in a way. Hopefully they check any crates that get uploaded to the website, because that could make it a security hole for more than just the developers if they didn't.
Something to potentially look into for the future is the Serenity crate. It has a similar collection of macro attributes groups a bunch of commands into a single collection that can be viewed at run time (or maybe only at compile time, I briefly looked at the crate once and never looked in depth, but I'm pretty sure it does something similar). I don't know what it does, but I'm pretty sure it doesn't rely on the compiler happening to evaluate the macros in the order you want it do and it could be worth figuring out how it does that and doing a follow up. I think it uses nested macro invocations (i.e., the Rust compiler does multiple expansion passes until everything is expanded, if I understand correctly, and from a glance at what using it like (with an outer `#[group]` attribute of some kind), I suspect that Serenity relies on this to get the necessary ordering instead of relying on internal plug in state).
I think you can make this concept work! 1. Make a global vec commands, and then dont store state in the macro, just after every function generate an append call, with the structure. If you are scared of reallocations, you can initiate vec with_capacity, so the first allocation will fully contain everything
@@Blubb3rbubYou'd have to wrap all append calls into another function, then you have to call that in main. Something like init_commands(); A bit sloppy though. You could do this concept with declarative macros instead so the order is guaranteed and you don't run into the parallel compilation issue.
I solved the issue of having state in a proc macro by having the attribute macros not do anything, and then having the function macro load the entire file and parse it such that it could look for the items I had annotated with the attribute macro in a global way. I think that would really suck without the syn/quote/proc_macro2 crates though
It is strange that Rust does not have a built-in way to get an actual AST. Obviously rustc is capable of parsing, so why not make that ability available to every macro?
It's because the code parsed by the compiler has to be syntactically correct. So just exposing the parser used by the compiler will not be that useful, they will have to expose APIs to modify the parser too. And I'm guessing they don't want to do that because it'll need to have an stable API. But I hope they will get around to exposing at least the parser generator they use so we don't have to use syn.
I believe Tsoding doesn't have a powerful rig so streaming would bog down the speed of the LSP and a slow LSP is more of a setback than it is of help, besides that he is very much used to manually looking at the documentation, locally if possible because he'd often do offline coding (there are offline streams uploaded here) and prefers to approach coding that way. I believe there's value in doing that, since becoming too reliant on getting answers from the internet makes you lazy, instead of actually resolving problems you end up just relying on whatever the next stackoverflow post has to offer and at that point, you aren't much of a programmer anymore
If he indeed has a less pwerful rig, what you said makes sense (its crazy how much resources there LSP's take). However on the point of looking things up, I think for rust especially and how documentation inherently works (using commnets to generate docs), using a '.' on the function and getting all available functions is particularly useful, usually IDE's will either show you the docstring or you can click into the function and view the docstring + actual code. For example i want to know if string has uppercase function, i simply do "mystring". and get all available methods, vs spending 30s-1min searching that. Time is money, and it adds up. @@javierflores09
@@anon_y_mousse good luck trying to create a macro that acts like an annotation. I dare you . The rules of the game is that the main source of the function should be kept separated from the actual listing of the function pointers. Now go
@@monad_tcp I wouldn't need to use a macro to implement this in C, and he probably doesn't need to in Rust either. All I'd need to do is define a struct and assign everything to it just after the last function is defined.
Why not just create the vector of commands at the beginning in the proc_macro_atribute? Maybe using std::sync::Once? After that you can go about adding items to the vector in each subsequent invocation.
Can you please go back to C? I thought this channel was about that. Rust is not production ready. It's community is toxic. Every Rust codebase is filled with unsafe blocks.. What is point of the language?
Amazing to watch someone programming so intuitively and knowledgeably in a language he doesn't even like.
Using Makefiles for rust compilation is the most cursed thing I've seen in a while
😂
Wasn't expecting a masterclass on procedural macros. Probably the best explanation I have come across on the topic.
I really like his organic way of teaching a certain topic
I love how you spend the time to motivate each additional design change... this is why you're my favorite youtuber
Concerning macro declaration order: you can write `use ;` to "elevate" the macro into a proper symbol that behaves like everything else (inlcuding being accessible by full module path etc).
It's a weird little quirk about declarative macros in Rust.
You probably already know it, but there is a library "clap" that will derive CLI interface from structs using #[derive] allowing you to have declarative style of defining your cli api
It’s an awesome lib, I use it occasionally, but Tsoding most likely does not give a crap about 3rd party libraries :)
Fuck clap. Why do you need thousands of lines of code for what should be a single function of 40 lines
@@L0wPressurec developers love to reinvent them wheels, let him be :)
@@igorfedenko8212 i'm not against it, i enjoy his streams :)
@@igorfedenko8212 Reinventing the wheel is not optimal when dealing with constraints or production code... this however is recreational programming ...the whole point of this is to learn something while having fun and diving under the hood! :)
I very much appreciated the "motivating example" at the beginning. It's extremely helpful to see what the "naive" way of doing something looks like so that the alternative way of doing the same thing makes more sense.
I was searching for someone to explain Rust Macro's. Didn't really felt like there was someone with a good example or explain video (at least not for me).
Now just watching this video because I like how you do things and explain it simple but clear, never thought you will just do the thing I was searching for/was stuck on just because you wanted to use it for yourself. Thank you
Just WOW. I'm programming my entire renderer, engine and game in Rust since 1.5 years ago and I'm not even at 10% of this level of efficiency and knowledge. The bell curve meme is so real.
what makes you think he's so good at rust? maybe it's the emacs
29:23 the typing sound with the beatboxing is definitely meme potential lol
He got some beef with the rust foundation after that thumbnail
rust foundation doesn't own Ferris (it's borrowed instead)
@@wetfloo Made me chuckle lol
This might be the best tsoding stream to date, hat off
And I think that's the reason Actix, Tower and other Rust libraries don't auto register services and controllers.
Thanks for the video. I really like it.
Thank you for walking through the problem and showing the naive solutions first. Very insightful!
Btw about exitcode, quite sure that when main returns a Result, the Err variant expands to 1 (which is trivial to impl by interpreting the enum as an numeric type by position, Ok variant being 0 (success) and Err variant being 1 (i.e exit code 0/1)).
From my understanding of what you were doing it's probably simple to just make main return a Result and so Err would automatically return "exit code" 1, and then you could add info to the Err itself (instead or alongside the eprintln()).
Edit: obviously you're then limited to 0/1 which isn't necessarily always what you would want.
44:00 you could've just done this
struct Command
where
T: Iterator,
{
name: &'static str,
description: &'static str,
run: fn(&str, T) -> ExitCode,
}
the reason why impl Iterator is not allowed in structs is because it hides the type.
structs need explicit generics to reveal the whole type.
functions don't need that because the type information is revealed in both cases
fn foo1(a: impl Iterator) {}
fn foo2(a: T) where T: Iterator {}
Foo
Foo
foo1(SomeType1)
foo2(SomeType2)
without generics in struct, you would instantiate two structs of different types that look the same
let f1: Foo = Foo { run: MyType1 }
let f2: Foo = Foo { run: MyType2 }
both f1 and f2 are of type Foo
instead of Foo and Foo
6:36 I'm watching a man slowly going insane.
always has been
Terry Davis arc incoming
That is an amazing video. I actually found out more about rust than I did from reading books. I have not touched anything proc macros related though.
Java is not the greatest language but for some usecases it makes things very convenient. With reflection you can get a list of all classes that implement an interface and turn that into a list of commands with a single function call. Something similar should be possible in other languages, but Java is where I have implemented it myself.
Rust macros really are another thing, most of the times you just need to do some basic processing/generation with the metadata, not some real dynamic thing at runtime, you just need to query the program structure at compiler time.
And you don't need to care about things like performance of reflection or security problems and reflection is a PITA to use because it literally turns your static language into a dynamic one.
I really liked the Rust solution for this problem.
Also, F# has a cool thing with TypeProviders that allow something similar, specially when you target the TypeProvider to a Type defined in the software. TypeProviders basically allow you to direct input any structured data as Types directly to the compiler itself, you basically compile an DLL/Assembly that loads inside the compiler and thus you can provide types for the compiler, its much more advanced than reflection, and simpler to use, almost like T4 in C#, but better and typed and you don't need to do something akin to "PHP", preprocessing like its "1990 C".
Reflection relies on a runtime and is really, really slow.
thanks for this bro. i personally loved every step of the ride. rustaocean son 🦀
A potential fun way to avoid the dependency on macro run order. (Note: I'm only at 1:25:00, so not sure if something else has come up later int he video)
Instead of tracking command state in the compiler plugin, you could track the state in a custom section of the elf and collect the arguments at runtime. The linux kernel does something similar for module init where it puts function pointers in an init section of the kernel elf and iterates the section at runtime. You could do something similar where you take your command struct, and essentially pack those structs into a special section of the binary at compile/link time. Then all you have to do to get the commands back is to iterate that special section as the first thing you do in main. The gcc equivalent would be to use __attribute__((section(...)))
I've done this a few times (never in rust though), and it's the only way I've managed to think of to get a compile time registry that doesn't depend on order of compilation
Doing some very quick googling, it seems like you could define static variables with #[link_section] and that would probably give the desired results. Maybe with some alignment constraints to think about
You can do the trick with sections, but you have to modify your linker script which is a pain, and I'm not sure even possible on windows, so it won't be cross platform.
You can also collect the commands at runtime if you register them via ".init" section, but once again it's super unsafe because it runs before main and once again it's not cross platform.
That sounds like such a pain that full-on meta-programming is preferable. Just output the source code you need. Probably much more understandable as well...
Rust just very recently got a parallel front-end in nightly
I don’t know much about Rust but I think the reason a prod macro has to be its own crate type is because it’s actually changing the way that the compiler itself processes the input.
I’d guess that rustc actually loads the dynamic library that the proc-macro crate generates for its own use.
Your explanations are lovely.
thank you.
the thumbnail will haunt my nightmares for many years to come
One of the best streams! Incredible as always
For making `command_list!()` execute last, you would probably need an extra crate that depends on all other crates that declare any Commands. Then, you can guarantee `command_list!()` is called after all the definition proc macros executed. Annoying, but I can't think of any other way with the current tools rust gives you.
In theory adding support in rustc for something like an `#[order(last)]` annotation for proc-macro crates should be relatively straightforward to support, I think.
Anyway you can now do a way shorter video about how it would be way simpler in zig/jai. At the very least it's good market pressure.
True.
Although the rustier way to do it would probably be to group every function inside an impl and use an attribute macro on the whole thing.
More parsing but no dependency to undefined compiler behavior.
Thanks mr zozin! That was really fun and educational!
Rustc was single threaded up until very recently XD
No wonder Gentoo users complain about Firefox build
I am trying to build my own command line application, even as a beginner I really love ur videos, they are easy enough for me to understand and I love how unique ur takes are sometimes XD
There is a crate called inventory, you can take a look into how it works to see how not to depend on the macros being called in order of their appearance in the code
Except that how inventory works is extremely technical with different implementations for different operating systems and it uses the link_section attribute. In other words, if you want to do it the way that inventory does it, then it's best to just use inventory.
I see, I never looked at the implementation myself. Maybe I will go and have a look at it first. Thanks
6:37 Новосибирский Джокер😎
Surely this is supported to some extent right?
how else would rust webframeworks be able to do #[get("/")] if it wasn't registering to somewhere?
How much for a lisp/scheme stream?
The way I normally approach this is by having a command line parser struct containing the map along with methods like add_flag or add_positional_argument that takes the the name, description and a lambda which binds the string argument to a variable captured by reference. Then after having added all commands, I call parser.parse(argc, argv). For adding subcommands you'd probably want a subcommand_start and subcommand_end as well though.
This reminds me of the time I did something similarly cursed to python type introspection to create argument parsers just from types. The library is called arcparse for anyone interested.
My milkshake brings all the args to the uppercase
@58:00 I suspect that it has something to do with the fact that proc macros are effectively compiler extensions, though I dunno what additional requirements this would place on the crate over being a normal dynamic library. It might be that they explicitly don't want it possible to include both normal code and macros from the same library, so when compiling you have to say "this is a normal library" or "this is a proc macro crate and if you want to use it for something else, it's not sanctioned by the compiler". Proc macros are DLL because they behave like an other Rust and because in large projects with complicated derive macros, they need to be fast. Which means you want them to be compiled. But they serve effectively as compiler extensions, so unless you want to recompile the entire compiler with your macro crate statically linked in, they need to be a DLL which the compiler can dynamically link against.
@1:04:00 The reason it's a token tree and not a string or an AST, based on documentation I've read is that
- it's a more consistent thing for the compiler to target than an AST (i.e., if additional language features add new syntax, the AST is going to have to change and this might break outstanding macros, but it'd take a damn radical change to change how the compiler tokenizes things). They're confident that matching parenthesis/square brackets/curly braces are always going to matched, but this leaves the details free to change without breaking macros. Additionally, Rust proc-macros are meant to not be limited to taking in Rust syntax and spitting out Rust syntax; they're designed to be able to take in, e.g., a DSL and spit out Rust syntax, and so while "parenthesis, etc., need to be balanced" is a reasonable limitation, they don't actually want to require that the input actually parse as valid Rust.
- it's much easier to work with than a raw string
@1:11:10 `for _ in _.into_iter()` is always redundant because the argument to a for loop must implement into iterator and into iterator is invoked on it, rather than implementing iterator.
@1:25:00 I think the reason to do it that way is just "able to express more things." Since you want them to be able to support DSLs which have different rules, better to have this be two tokens rather than one.
Can't you like wrap everything in a mutex and yolo it though?
procedural macros look like rust's take on ocaml's ppxs
You blew my fucking mind with that COUNT.
58:10 - It's probably it's own type because it's a dynlib that links against the compiler itself.
*OH BOI*
the singing was hilarious
thank you 😊 🎉
2:00:00 I think rust will have to gurantee the order at least per single source file. (The only other method than using your macro list would be even worse - injection in the tree)
1:53:30 Maybe collecting the literal (instead of String) and inserting it again avoids quote handling,
actually makes sense, that it isn't a proper ast, since you want to do stuff like compile time checked sql which obviously isn't in rust syntax. at least that's what i assume why? lol
Edit: really wonder if there is a way to keep state in a proc macro that would be "rust approved" ^^
1:03:03 wouldn't it be just a syntax tree, but not an abstract one? It's somewhat parsed to do the grouping, but not fully made sense yet?
zozin i LUV U
I think I know precisely what's going to happen. (when I saw that .so)
Rust is going to load that inside the compiler itself, the compiler is going to do dlopen
Hey, like your content.
What Linux distro do you use?
Is this possible to build as statically linked binary?
Could you just use something a static flag to check is the command list is generated, and if so prohibit any more #[command] macro calls?
That was some gnarly code. The fact that the compiler allows for plugins like that is kind of horrific in a way. Hopefully they check any crates that get uploaded to the website, because that could make it a security hole for more than just the developers if they didn't.
Something to potentially look into for the future is the Serenity crate. It has a similar collection of macro attributes groups a bunch of commands into a single collection that can be viewed at run time (or maybe only at compile time, I briefly looked at the crate once and never looked in depth, but I'm pretty sure it does something similar).
I don't know what it does, but I'm pretty sure it doesn't rely on the compiler happening to evaluate the macros in the order you want it do and it could be worth figuring out how it does that and doing a follow up. I think it uses nested macro invocations (i.e., the Rust compiler does multiple expansion passes until everything is expanded, if I understand correctly, and from a glance at what using it like (with an outer `#[group]` attribute of some kind), I suspect that Serenity relies on this to get the necessary ordering instead of relying on internal plug in state).
6:40 lol
1:09:35 it looks like a security vulnerability waiting to happen
not really, it is at compile time. not runtime
@@belst_ Still insecure for the developer making use of it.
1:23:12 just put it in an input and display the Tokens for debugging
I think you can make this concept work! 1. Make a global vec commands, and then dont store state in the macro, just after every function generate an append call, with the structure. If you are scared of reallocations, you can initiate vec with_capacity, so the first allocation will fully contain everything
Where to generate the append call? Outside of any function body where the function is declared?
@@Blubb3rbubYou'd have to wrap all append calls into another function, then you have to call that in main. Something like init_commands();
A bit sloppy though. You could do this concept with declarative macros instead so the order is guaranteed and you don't run into the parallel compilation issue.
@@stysner4580 But then you still need state in the macro to know which append calls to generate.
You could use declarative macros:
commands_init!();
def_command!("test", "print test", test_fn);
fn test_fn() {}
commands_finalize!();
wait until he finds out main() is actually not the entry point of the executable.
java annotations FTW
Hmm, parsing a literal to string and then back to literal along with unwrap.. Bet the rust devs are going to crazy about the error handling though
I solved the issue of having state in a proc macro by having the attribute macros not do anything, and then having the function macro load the entire file and parse it such that it could look for the items I had annotated with the attribute macro in a global way. I think that would really suck without the syn/quote/proc_macro2 crates though
ummm... just so you know, you can use String::into :p
First!
And Rust ftw, of course
we are always last!
@@elden8359 fr man we really should start watching the streams but i always miss them
this man is using rust in 2023
@@RedstonekPL he's the choosen one
It is strange that Rust does not have a built-in way to get an actual AST. Obviously rustc is capable of parsing, so why not make that ability available to every macro?
It's because the code parsed by the compiler has to be syntactically correct. So just exposing the parser used by the compiler will not be that useful, they will have to expose APIs to modify the parser too. And I'm guessing they don't want to do that because it'll need to have an stable API. But I hope they will get around to exposing at least the parser generator they use so we don't have to use syn.
Its by design. This way you can implement crazy dsl's. If it is full AST libraries like leptos could not exist.
Why not use a LSP? Would save a lot of time on having to read documentation
I believe Tsoding doesn't have a powerful rig so streaming would bog down the speed of the LSP and a slow LSP is more of a setback than it is of help, besides that he is very much used to manually looking at the documentation, locally if possible because he'd often do offline coding (there are offline streams uploaded here) and prefers to approach coding that way.
I believe there's value in doing that, since becoming too reliant on getting answers from the internet makes you lazy, instead of actually resolving problems you end up just relying on whatever the next stackoverflow post has to offer and at that point, you aren't much of a programmer anymore
If he indeed has a less pwerful rig, what you said makes sense (its crazy how much resources there LSP's take). However on the point of looking things up, I think for rust especially and how documentation inherently works (using commnets to generate docs), using a '.' on the function and getting all available functions is particularly useful, usually IDE's will either show you the docstring or you can click into the function and view the docstring + actual code. For example i want to know if string has uppercase function, i simply do "mystring". and get all available methods, vs spending 30s-1min searching that. Time is money, and it adds up. @@javierflores09
If you advise that Rust should be learnt as a programming language, Would you think of teaching Rust on here with a tutorial series?
*oof*
It would be cool to see this implemented in C
It'd be easier in C.
@@anon_y_mousse good luck trying to create a macro that acts like an annotation.
I dare you .
The rules of the game is that the main source of the function should be kept separated from the actual listing of the function pointers.
Now go
@@monad_tcp I wouldn't need to use a macro to implement this in C, and he probably doesn't need to in Rust either. All I'd need to do is define a struct and assign everything to it just after the last function is defined.
@@anon_y_mousse but then its not statically compiled, but I know
@@monad_tcp I'm not sure what you meant to say there, but it would certainly be statically compiled.
Why not just create the vector of commands at the beginning in the proc_macro_atribute? Maybe using std::sync::Once? After that you can go about adding items to the vector in each subsequent invocation.
You should teach at university
man i wish i could these stuff
1:38:30 It would be great if you could do something like `match args_iter { [ Literal(first_lit), Punct(Comma), Literal(second_lit) ] => ... }`
main - 4.5M
libcommand -6M
Can your C do that? I don't think so.
it looks like a bad reflection )
Bro just discovered abstraction
Can you please go back to C? I thought this channel was about that.
Rust is not production ready.
It's community is toxic. Every Rust codebase is filled with unsafe blocks.. What is point of the language?
are u serious
let the guy code what he wants to code? This channel is just whatever he wants to code, when was it ever "just c"?
You gotta be trolling right ?
lmao
it was never just C, it's just recreational programming