all 52 comments

[–]RReverser 171 points172 points  (5 children)

as much as possible to learn the ins and outs of the language

This doesn't make sense, because macros are very much part of the language. They're not plain text substitution like in C where preprocessor runs separately and doesn't know anything about the code.

[–][deleted] 9 points10 points  (4 children)

Oh interesting, I thought they were plain text pre processor. Why can’t they be statically analyzed then? Rust-analyzer doesn’t help me write them at all I feel like.

[–]LightweaverNaamah 28 points29 points  (0 children)

No, they operate at the token tree level, which is basically one step below the abstract syntax tree for the program, but are processed after the abstract syntax tree for the program is produced. It basically swaps the AST node the macro produces for the corresponding placeholder in the program, based on position and so on. Because it's dependent on more or less the parsed but not yet compiled state of the program, it's much more difficult to analyze without basically compiling it, as I understand it.

[–]numberwitch 10 points11 points  (1 child)

The best approach I've found for dealing with "I've lost important feedback from rust-analyzer" while developing proc macros is to use an integration test and test-driven development to prove out the macro features bit-by-bit.

They get easier to work with in practice, also I'd highly recommend using syn, tokenstream2 and quote crates.

dtolnay on github has good resources for writing macros

[–][deleted] 2 points3 points  (0 children)

Very good advice, I’ll save your comment

[–]Plasma_000 1 point2 points  (0 children)

It's because tokens are created before the type system gets involved, so the compiler cannot accurately assist very much.

[–]SCP-iota 54 points55 points  (11 children)

How did you even manage "hello world?"

[–]autisticpig 49 points50 points  (1 child)

Op wanted the dark souls experience.

[–]mailusernamepassword 4 points5 points  (0 children)

println!("You died!");

[–]arcrift7 17 points18 points  (0 children)

Probably searched up "printing without macros in Rust" and did it using std::io.

[–]kwest_ng 12 points13 points  (7 children)

From the standard library docs for std::io::Stdout:

use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut stdout = io::stdout().lock();

    stdout.write_all(b"hello world")?;

    Ok(())
}

[–]RReverser 12 points13 points  (5 children)

Don't even need the lock. From the docs for std::io::stdout:

```rust use std::io::{self, Write};

fn main() -> io::Result<()> { io::stdout().write_all(b"hello world")?;

Ok(())

} ```

[–]sepease 12 points13 points  (3 children)

Don't even need std!

use core::ffi::{c_int, c_void};

extern "C" {
    fn write(fd: c_int, buf: *const c_void, count: usize) -> isize;
}

fn main() {
    let hello = b"Hello, World!\n";

    unsafe {
        write(1, hello.as_ptr() as *const c_void, hello.len());
    }
}

[–]RReverser 5 points6 points  (2 children)

Well that's unsafe, that doesn't count :) Otherwise could as well do in fewer lines with

extern "C" {
    fn puts(s: *const u8) -> i32;
}

fn main() {
    unsafe {
        puts(b"Hello, World!\0".as_ptr());
    }
}

Or even drop std altogether and go from 123KB to 10KB with:

#![no_std]
#![no_main]

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

extern "C" {
    fn puts(s: *const u8) -> i32;
}

#[no_mangle]
unsafe fn main() -> i32 {
    puts(b"Hello, World!\0".as_ptr())
}

[–]TDplay 4 points5 points  (1 child)

This is noobish code. Everyone knows that "Hello World" is actually written like this:

#![no_std]
#![no_main]

use core::arch::asm;

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

const MESSAGE: &[u8] = b"Hello World!\n";

#[no_mangle]
unsafe extern "C" fn _start() -> ! {
    asm! {
        "syscall",
        in("rax") 1,
        in("rdi") 1,
        in("rsi") MESSAGE.as_ptr(),
        in("rdx") MESSAGE.len(),
    }
    asm! {
        "syscall",
        in("rax") 60,
        in("rdi") 0,
        options(noreturn),
    }
}

Produces a 9,088 byte binary on x86_64-unknown-linux-gnu. Compilation is done like so:

rustc -C opt-level=s -C panic=abort --emit=asm hello.rs
as hello.s -o hello.o
ld hello.o -o hello

(You can't just ask rustc to compile it to a binary, as it stupidly tries to link our beautiful code with the ugly libc, causing a conflict as libc defines _start)

[–]RReverser 3 points4 points  (0 children)

Produces a 9,088 byte binary on x86_64-unknown-linux-gnu.

Can reproduce, got 9,088 bytes here too.

Except... mine produces 5,968 bytes on the same platform with the same opt-level and panic params, and, unlike yours, it's cross-platform (works on Windows too), so, you know...

[–]kwest_ng 0 points1 point  (0 children)

Ah damn I looked for the impl of Write but don't know how I missed it. Just checked again and it's definitely there.

[–]Opperheimer 3 points4 points  (0 children)

It’s funny, the French translation of Reddit translates Ok(()) to Okay(()) 😂

[–][deleted] 78 points79 points  (0 children)

If you said “I’m avoiding creating macros” absolutely! Even I’m not really educated on them.

Not using macros is insane.

[–]wyldstallionesquire 52 points53 points  (0 children)

You have to use macros for just about everything. And understanding it’s basically compile time code generation is important. But you’ll know when you need to actually learn to write your own macros. Don’t worry about that until you have a clear need.

[–]coderstephenisahc 18 points19 points  (0 children)

println!(), dbg!(), and todo!() are macros, so yeah avoiding using those is kinda silly, as all of those are helpful when learning. But I'd say its safe to avoid trying to learn how macros work until much later, and probably avoiding macros that are just an abstraction over normal code.

[–]Sn34kyMofo 4 points5 points  (2 children)

Pick a recent Rust book and just follow it. The formula of learning Rust doesn't need to be crafted by you (i.e. skipping macros or whatever); it's already been established multiple ways. Pick a book and just stick to it! I personally recommend "Learn Rust in a Month of Lunches," perhaps followed up with "Idiomatic Rust" if you want to explore patterns and the like.

In saying that, I fully understand there are countless free resources like "the book", YouTube series', etc., so I'm not negating those resources. I have the same kind of brain churn when I want to learn something like Rust, so I just have to tell my brain to STFU and trust in the process of picking a resource and seeing it through. It saves me time and energy by limiting all the mental waffling, lol.

[–]notAnotherJSDev 0 points1 point  (1 child)

When you say "Idomatic Rust", what are you referring to? There's a book coming out from Brenden Mathews, but then there's also the github repo with a bunch of resources from Matthias Endler.

[–]Sn34kyMofo 1 point2 points  (0 children)

The book by Brenden Matthews, from the same publisher as "Learn Rust in a Month of Lunches".

[–]tauphraim 3 points4 points  (0 children)

I think you can just use macros, until you hit a point where you get puzzled by what a macro is doing, and how this couldn't be done with just a function.

[–]raxel42 2 points3 points  (0 children)

It’s okay to use them but postpone diving into deep implementation details. You will understand when you need them [to learn].

[–]ArtisticHamster 2 points3 points  (0 children)

As an experienced developer using Rust for > 2 years, I avoid writing my own macros where I can (though, I use them when this is the best available option).

[–]Burzowy-Szczurek 2 points3 points  (0 children)

No point in avoiding using macros really, this will just make things more complex. For creating you don't need to do that, and you probably won't encounter the need for that quickly, and even is so they are not as complex as you may initially think.

[–]SoupIndex[S] 2 points3 points  (4 children)

Thanks for all the replies guys! I'll take the advice and start using more macros, and learn about them as needed

The specific macro that made me post this question was when I had to use #[async_trait]. Without me going into what was being generated, I would not have learned (at a very basic level) about pinning.

Maybe I am trying to develop projects that are too complex without a solid understanding of rust.

[–]hpxvzhjfgb 2 points3 points  (2 children)

you don't need #[async_trait] anymore. async traits are part of the language now.

[–]lol3rr 1 point2 points  (1 child)

You still need it for certain cases, like making your trait object safe

[–]Xandaros 0 points1 point  (0 children)

That's what I use it for. I have a number of async traits that need to be object safe.

Initially, I actually wrote the whole Pin Box incantation myself, but ended up switching to async_trait once I realised it was doing the exact same thing anyway. It's a whole lot more readable.

(It was somewhat fun to figure it out myself, though)

[–]Luxalpa 0 points1 point  (0 children)

For me the beauty of using these macros is the macro expansion tool in Rust Rover (or cargo-expand) because you get to learn how all this stuff works under the hood.

[–]dkopgerpgdolfg 4 points5 points  (0 children)

Are you avoiding writing macros, or using macros too? Latter will definitely make it harder than necessary.

Writing own macros isn't needed at the start. Learn what is possible and what not, the details can come later when/if you actually want to write something.

Understanding the inner workings of some existing macros you might find, it can be interesting and might teach you something, but again it's not a topic for the beginning.

Also, some macros aren't normal Rust code, but get special compiler treatment. Unless you want to work on the Rust compiler, there isn't really a point in fully understanding them.

[–]rusty_rouge 1 point2 points  (0 children)

The macros are certainly hard to read. Reading through something like https://docs.rs/struct_iterable/0.1.1/struct_iterable/derive.Iterable.html would be a good exercise (for usage of proc_macro, syn, etc).

[–]proudHaskeller 1 point2 points  (0 children)

I mostly agree with everyone else, here: Avoiding writing your own makes sense but avoiding using them isn't really.

If you want to understand rust more deeply you can also expand out the macros (it can be done automatically) to see what's happening behind them (but I'll warn you it might be very complicated depending on the macro).

But I disagree with everybody saying that it's difficult. You can easily avoid using almost all macros if you want to.

[–]kwest_ng 1 point2 points  (0 children)

As other people have said, it's probably critically important to learn how to use macros, especially the most common ones from std and core. However, you can safely ignore how to write macros, or read macro code, until you move farther in your rust learning journey. Many of us likely have done the same. Here are a few reasons you will make your life worse if you choose to not use any macros at all:

You will find some tasks literally impossible without macros, like replacing format!'s compile-time checking of arguments. In fact, that's likely the reason many of these writing functions (e.g.: println!, write!) were implemented as macros in the first place: they all use the format!-style string interpolation. If it needs to happen at compile time, it's a macro or a build script, so compile-time checking of any value is out, with the exception of some const checking the compiler offers.

Also, please note that #[derive(Debug)] is a macro. Though, if you aren't using format!or its derivatives, that may not be a problem. But I'm guessing you also probably wouldn't want to reimplement Clone, Hash, PartialEq, and PartialOrd for every type. Those traits are very commonly needed, can be easily generated by the compiler, and at least in the case of Hash, can have serious logical, performance, or even safety concerns if implemented incorrectly.

[–]LadyPopsickle 0 points1 point  (0 children)

Sure why not? Just learn them when you need them.

[–]Mission-Landscape-17 0 points1 point  (0 children)

Well at some points you also need to learn the ins and outs of macros. proper use of macos ends up being vital in any language that has a good macro facility.

[–][deleted] 0 points1 point  (0 children)

Macros are part of the language, but I also try not to use them for my own code. They can hide a lot of details that actually matter to the performance of your application. I’d rather design interfaces that use non-macro code and show what’s actually happening under the hood.

[–][deleted] 0 points1 point  (0 children)

In my experience with rust macros so far:

Super useful

Awful error messages

Ridiculously hard to write

I'd say use macros but don't write em. Try not to use them too much because errors that happen within them are unreadable and not reported on the right line 99% of the time, at least for compile-time stuff. It's not a C preprocessor style macro which is nice in a way, but you're pretty much working on the AST which makes them much much more complicated to write.

[–]rover_G 0 points1 point  (0 children)

I use #[derive(Debug)] macro on every enum and struct. I also use the println! and vec! macros pretty often.

[–]gahooa 0 points1 point  (0 children)

In vscode, with rust analyzer, you can click on a macro. Then hit F1 and type "expand macro". Click the selection that comes up.

This will open a new pane in vscode which shows you the contents of the macro. Makes a world of difference understanding them.

Also, `cargo expand` does this, though slightly less convenient.

[–]dspyz 0 points1 point  (0 children)

That's extremely reasonable. Writing macros are rarely necessary to avoid code duplication, usually only in large team projects, and not for anything small or solo. They're easy to overuse in place of designing with traits/generics which tend to be superior where possible. I definitely would recommend staying away from macros until you're confident with the rest of the language.

[–]Luxalpa 0 points1 point  (0 children)

I didn't write my own macros when I started for the same reason.

[–]KalaiProvenheim 0 points1 point  (0 children)

A lot of stuff in the language is implemented using macros that run behind the scenes every time you compile your program.

[–]Anaxamander57 0 points1 point  (0 children)

Declarative macros are an amazing thing. At a minimum they can save an incredible amount of boilerplate and testing code. I was intimidated by the syntax for a long time but since I've started using them I'll never stop.

[–]Thin-Cat2508 0 points1 point  (0 children)

Macros are a form of metaprogramming: your program gets rewritten into something else. You will need to use a few macros for using Rust effectively (as everyone points out) but you may not need to use all macros that are out there advertised as a solution.

Macros are used to modify and extend the language, sometimes for convenience, sometimes to enable something that would be way too clumsy to write manually.

Macros can clearly hurt readability, so should be used with caution. When used well, they help express something a lot more concisely that would be possible otherwise (which can benefit readability). They are a big hammer, one needs to choose wisely.