ฉันจะใช้มาโครข้ามไฟล์โมดูลได้อย่างไร


93

ฉันมีสองโมดูลในไฟล์แยกกันภายในลังเดียวกันซึ่งลังmacro_rulesเปิดใช้งาน ฉันต้องการใช้มาโครที่กำหนดไว้ในโมดูลหนึ่งในโมดูลอื่น

// macros.rs
#[macro_export] // or not? is ineffectual for this, afaik
macro_rules! my_macro(...)

// something.rs
use macros;
// use macros::my_macro; <-- unresolved import (for obvious reasons)
my_macro!() // <-- how?

ขณะนี้ฉันพบข้อผิดพลาดของคอมไพเลอร์ " macro undefined: 'my_macro'" ... ซึ่งสมเหตุสมผล ระบบมาโครทำงานก่อนระบบโมดูล ฉันจะหลีกเลี่ยงสิ่งนั้นได้อย่างไร


ไม่ควรใช้module::my_macro!()?
u_mulder

2
nope (ไม่ใช่ afaik) - รายงานคำนำหน้าโมดูลถูกละเว้น (ตามข้อความคอมไพเลอร์)
ผู้ใช้

คำตอบ:


131

มาโครภายในลังเดียวกัน

#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}

bar!();    // works

#[macro_use]หากคุณต้องการใช้แมโครในลังเดียวกันโมดูลแมโครของคุณถูกกำหนดไว้ในความต้องการของแอตทริบิวต์

สามารถใช้มาโครได้หลังจากกำหนดไว้แล้วเท่านั้น ซึ่งหมายความว่าสิ่งนี้ใช้ไม่ได้:

bar!();  // ERROR: cannot find macro `bar!` in this scope

#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}

มาโครข้ามลัง

กับการใช้งานmacro_rules!แมโครจากลังอื่น ๆ #[macro_export]แมโครตัวเองต้องการแอตทริบิวต์ use crate_name::macro_name;ลังการนำเข้านั้นสามารถนำเข้าแมโครผ่าน

ลัง util

#[macro_export]
macro_rules! foo {
    () => ()
}

ลัง user

use util::foo;

foo!();

โปรดทราบว่ามาโครจะอยู่ที่ระดับบนสุดของลังเสมอ ดังนั้นแม้ว่าfooจะเป็นภายในmod bar {}ที่userลังจะยังคงต้องมีการเขียนuse util::foo;และการไม่ได้ use util::bar::foo;

ก่อนเกิดสนิม 2018 คุณต้องนำเข้ามาโครจากลังอื่นโดยการเพิ่มแอตทริบิวต์#[macro_use]ในextern crate util;คำสั่ง utilที่จะนำเข้าแมโครทั้งหมดจาก หรือ#[macro_use(cat, dog)]อาจใช้เพื่อนำเข้ามาโครcatและdog. ไวยากรณ์นี้ไม่จำเป็นอีกต่อไป

ข้อมูลเพิ่มเติมสามารถดูได้ในสนิมเขียนโปรแกรมภาษาบทที่เกี่ยวกับมาโคร


27
"มาโครสามารถใช้ได้หลังจากที่ได้กำหนดไว้แล้วเท่านั้น" - นี่เป็นกุญแจสำคัญเพราะคุณสามารถพบข้อผิดพลาดนั้นได้แม้ว่าคุณจะทำสิ่งอื่น ๆ ทั้งหมดที่กล่าวถึงอย่างถูกต้อง ตัวอย่างเช่นหากคุณมีโมดูลmacrosและfoo(ซึ่งใช้มาโครจากmacros) และคุณแสดงรายการตามลำดับตัวอักษรใน lib.rs หรือ main.rs ของคุณ foo จะถูกโหลดก่อนมาโครและโค้ดจะไม่คอมไพล์
neverfox

7
^ เคล็ดลับสำหรับมืออาชีพ - สิ่งนี้ทำให้ฉันได้รับ
semore_1267

3
นอกจากนี้โปรดทราบว่าสำหรับการใช้มาโครภายใน#[macro_use]แอ็ตทริบิวต์ควรอยู่ในทุกโมดูลและโมดูลพาเรนต์ ฯลฯ จนกว่าจะถึงจุดที่คุณจำเป็นต้องใช้
10

คำตอบนี้ไม่ได้ผลสำหรับฉัน โมดูลที่ประกาศมาโครมี#[macro_use]และประกาศก่อนใน lib.rs - ยังไม่ทำงาน คำตอบของ @ Ten ช่วยได้และฉันก็เพิ่ม#[macro_use]ไปที่ด้านบนของ lib.rs - แล้วมันก็ใช้ได้ แต่ฉันยังไม่แน่ใจว่าแนวทางปฏิบัติที่ดีที่สุดคืออะไรเนื่องจากฉันอ่านที่นี่ว่า "คุณไม่ได้นำเข้ามาโครจากโมดูลอื่นคุณส่งออกมาโครจากโมดูลการกำหนด"
Sorin Bolos

ฉันมักจะลืมว่ามาโครของ Rust ทำงานกับโมดูลอย่างไร มันเป็นระบบที่แย่มากและหวังว่าสักวันจะมีระบบที่ดีกว่านี้
Hutch Moore

20

คำตอบนี้ล้าสมัยเนื่องจาก Rust 1.1.0-stable


คุณต้องเพิ่ม#![macro_escape]ที่ด้านบนของmacros.rsและเก็บไว้ใช้mod macros;เป็นที่กล่าวถึงในคู่มือแมโคร

$ cat macros.rs
#![macro_escape]

#[macro_export]
macro_rules! my_macro {
    () => { println!("hi"); }
}

$ cat something.rs
#![feature(macro_rules)]
mod macros;

fn main() {
    my_macro!();
}

$ rustc something.rs
$ ./something
hi

สำหรับการอ้างอิงในอนาคต,

$ rustc -v
rustc 0.13.0-dev (2790505c1 2014-11-03 14:17:26 +0000)

ฉันพลาดคุณสมบัตินั้นไปโดยสิ้นเชิง ขอบคุณ!
ผู้ใช้

4
BTW #[macro_export]แอตทริบิวต์ไม่จำเป็นที่นี่ จำเป็นเฉพาะในกรณีที่ควรส่งออกมาโครไปยังผู้ใช้ลังภายนอก หากใช้มาโครภายในลังเท่านั้น#[macro_export]ไม่จำเป็นต้องใช้
Vladimir Matveev

1
ขอบคุณมากสำหรับคำตอบ ฉันแค่อยากจะเพิ่มว่าถ้าsomething.rsไฟล์ของคุณใช้โมดูลอื่นเช่นด้วยmod foobar;และfoobarโมดูลนี้ใช้มาโครจากmacro.rsนั้นคุณต้องใส่ไว้mod macro; ก่อน mod foobar;เพื่อให้โปรแกรมคอมไพล์ สิ่งเล็กน้อย แต่นี่ไม่ใช่ IMO ที่ชัดเจน
conradkleinespel

2
(nb คำตอบนี้ล้าสมัยแล้วฉันยอมรับคำตอบล่าสุดที่ให้โดย Lukas)
ผู้ใช้

7

การเพิ่ม#![macro_use]ที่ด้านบนของไฟล์ที่มีมาโครจะทำให้มาโครทั้งหมดถูกดึงเข้าไปใน main.rs

ตัวอย่างเช่นสมมติว่าไฟล์นี้เรียกว่า node.rs:

#![macro_use]

macro_rules! test {
    () => { println!("Nuts"); }
}

macro_rules! best {
    () => { println!("Run"); }
}

pub fn fun_times() {
    println!("Is it really?");
}

main.rs ของคุณจะมีลักษณะดังต่อไปนี้:

mod node;  //We're using node.rs
mod toad;  //Also using toad.rs

fn main() {
    test!();
    best!();
    toad::a_thing();
}

สุดท้ายสมมติว่าคุณมีไฟล์ชื่อ toad.rs ซึ่งต้องใช้มาโครเหล่านี้ด้วย:

use node; //Notice this is 'use' not 'mod'

pub fn a_thing() {
  test!();

  node::fun_times();
}

สังเกตว่าเมื่อไฟล์ถูกดึงเข้าสู่ main.rs ด้วยmodแล้วไฟล์ที่เหลือของคุณจะสามารถเข้าถึงไฟล์เหล่านี้ได้ผ่านuseคีย์เวิร์ด


ฉันเพิ่มการชี้แจงเพิ่มเติม สำหรับสนิม 1.22.1 สิ่งนี้ใช้งานได้
Luke Dupin

คุณแน่ใจไหม? เอกสาร #! [macro_use] (ไม่ใช่ # [macro_use]) อยู่ที่ไหน ฉันหาไม่เจอ มันใช้ไม่ได้ที่นี่
Markus

สิ่งนี้ใช้ได้ผลเมื่อฉันโพสต์ระบบรวมของ Rust นั้นเป็นระเบียบที่น่ากลัวเป็นไปได้ทั้งหมดนี้จะไม่ทำงานอีกต่อไป
Luke Dupin

หมายเหตุ @Markus ว่า#![macro_use]คำสั่งภายในแมโครโมดูลไม่ได้ออกไปข้างนอก #![...]สอดคล้องไวยากรณ์คุณลักษณะที่มีผลบังคับใช้กับขอบเขตของพวกเขาที่มีเช่น#![feature(...)](ชัดนี้จะไม่ทำให้รู้สึกว่าเขียนเป็น#[feature(...)]มันหมายว่าจะต้องคอมไพเลอร์เปิดใช้งานคุณลักษณะบางอย่างในรายการที่เฉพาะเจาะจงในลังมากกว่าลังรากทั้งหมด) ดังที่ @LukeDupin กล่าวว่าระบบโมดูลนั้นยุ่งเหยิงแม้ว่าอาจจะด้วยเหตุผลที่แตกต่างจากที่เห็นในตอนแรก
ผู้ใช้

ฉันหวังว่าคำตอบนี้จะกล่าวถึงวิธีการก่อสร้างที่ไม่ใช่สำนวน (นอกจากนี้ฉันชอบคำตอบ) แม้ว่าจะมีความเป็น (ไม่ใช่) - ความเป็นเอกลักษณ์ แต่ก็น่าสนใจเพราะการวางไว้ข้างๆรูปแบบสำนวนทำให้เห็นได้ชัดอย่างเจ็บปวดว่ามาโครโต้ตอบกับระบบโมดูลในลักษณะที่แตกต่างจากโครงสร้างปกติ หรืออย่างน้อยก็ให้กลิ่นที่รุนแรง (ตามที่ @Markus แสดงให้เห็นว่ามีกลิ่นจับด้วย)
ผู้ใช้

2

ฉันเจอปัญหาเดียวกันใน Rust 1.44.1 และวิธีนี้ใช้ได้กับรุ่นที่ใหม่กว่า (รู้จักกันในชื่อ Rust 1.7)

สมมติว่าคุณมีโครงการใหม่เป็น:

src/
    main.rs
    memory.rs
    chunk.rs

ในmain.rsคุณต้องใส่คำอธิบายประกอบว่าคุณกำลังนำเข้ามาโครจากแหล่งที่มามิฉะนั้นจะไม่ทำเพื่อคุณ

#[macro_use]
mod memory;
mod chunk;

fn main() {
    println!("Hello, world!");
}

ดังนั้นในmemory.rsคุณสามารถกำหนดมาโครได้และคุณไม่จำเป็นต้องมีคำอธิบายประกอบ:

macro_rules! grow_capacity {
    ( $x:expr ) => {
        {
            if $x < 8 { 8 } else { $x * 2 }
        }
    };
}

ในที่สุดคุณก็สามารถใช้มันในchunk.rsและคุณไม่จำเป็นต้องรวมมาโครไว้ที่นี่เพราะมันทำใน main.rs:

grow_capacity!(8);

คำตอบ upvotedทำให้เกิดความสับสนสำหรับผมกับเอกสารนี้โดยตัวอย่างก็จะเป็นประโยชน์มากเกินไป


คำตอบที่ได้รับการยอมรับอย่างแท้จริง#[macro_use] mod foo {มีที่เป็นบรรทัดแรกของการป้องกันรหัสแรก:
Shepmaster

1
@Shepmaster คำตอบที่ได้รับการโหวตมีความหมายของมาโครและคำสั่งนำเข้าในที่เดียวกันดังนั้นจึงทำให้เกิดความสับสน (สำหรับฉัน) ฉันใช้#[macro_use]ในนิยาม คอมไพเลอร์ไม่ได้บอกว่าวางผิดตำแหน่ง
knh190

คุณอาจต้องการที่จะ re-read doc.rust-lang.org/book/...แล้ว
Shepmaster

ขอบคุณสำหรับคำตอบนี้! ฉันรู้สึกสับสนกับคำตอบที่ได้รับการยอมรับเช่นกันและคิดไม่ออกจนกว่าฉันจะอ่านคำอธิบายของคุณ
Prgrm.celeritas

@Shepmaster ไม่มีการกล่าวถึงวิธีการทำงานของมาโครในส่วนที่คุณลิงก์ไป คุณต้องการเชื่อมโยงไปยังส่วนอื่น ๆ ของหนังสือหรือไม่?
detly
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.