I am creating an attribute macro that can be invoked with (a path to) some type amongst its attributes:
#[my_macro(path::to::SomeType)]
This macro generates a module, within which the specified type is to be used:
mod generated {
use path::to::SomeType;
}
However, from within that generated module, the path as provided may no longer be correct:
if the path is prefixed with ::, it remains correct—I can determine this within the proc macro;
otherwise, if the path starts with an identifier that is in a prelude (such as that of an external crate), it remains correct—I can't see any way to determine this within the proc macro;
otherwise it must now be prefixed with super:: for resolution to succeed.
Of course, I could require users of the macro to provide a path that will be correct from within the generated module, but that would not be very ergonomic.
Instead, I'm currently generating a type alias within the parent module:
type __generated_SomeType = path::to::SomeType;
mod generated {
use super::__generated_SomeType as SomeType;
}
However this is not only a tad clunky, but it furthermore pollutes the parent namespace.
Is there a better way?
I don't think there's a satisfying solution. But there's an alternative, and a way to improve your current approach.
The alternative is to use a const item:
const _: () = {
// ... your generated code here
};
It can still cause name collisions with items from the parent scope, but at least the generated items aren't visible outside of the const. This is what most macro authors do.
If you don't want to do that, you can go with your current approach, but you should add #[doc(hidden)] to the __generated_SomeType type, so it doesn't show up in documentation. Also, you should use an import instead of a type alias to support generic types, as suggested by #ChayimFriedman:
#[doc(hidden)]
use path::to::SomeType as __generated_SomeType;
I was just about to suggest combining these to approaches by putting a module inside a const item, but it doesn't work:
const _: () = {
use path::to::SomeType as __generated_SomeType;
mod generated {
// doesn't work :(
use super::__generated_SomeType as SomeType;
}
};
Rust complains that __generated_SomeType doesn't exist in the parent module.
Related
I have been using the #[tokio::main] macro in one of my programs. After importing main and using it unqualified, I encountered an unexpected error.
use tokio::main;
#[main]
async fn main() {}
error[E0659]: `main` is ambiguous
--> src/main.rs:3:3
|
3 | #[main]
| ^^^^ ambiguous name
|
= note: ambiguous because of a name conflict with a builtin attribute
= note: `main` could refer to a built-in attribute
I've been scouring the documentation but I haven't been able to find this built-in #[main] attribute described anywhere. The Rust Reference contains an index of built-in attributes. The index doesn't include #[main], though it does include an attribute named #[no_main].
I did a search of the rustlang/rust repository, and found some code that seems related, but it seems to use a pair of macros named #[start] and #[rustc_main], with no mention of #[main] itself. (Neither of those macros appears to be usable on stable, with #[start] emitting an error that it's unstable, and #[rustc_main] emitting an error that it's only meant for internal use by the compiler.)
My guess from the name would have been that it's meant to mark a different function as an entry-point instead of main, but it also doesn't seem to do that:
#[main]
fn something_else() {
println!("this does not run");
}
fn main() {
println!("this runs");
}
Rust Analyzer didn't have much to offer:
What does the built-in #[main] attribute do, aside from conflicting with my imports? 😉
#[main] is an old, unstable attribute that was mostly removed from the language in 1.53.0. However, the removal missed one line, with the result you see: the attribute had no effect, but it could be used on stable Rust without an error, and conflicted with imported attributes named main. This was a bug, not intended behaviour. It has been fixed as of nightly-2022-02-10 and 1.59.0-beta.8. Your example with use tokio::main; and #[main] can now run without error.
Before it was removed, the unstable #[main] was used to specify the entry point of a program. Alex Crichton described the behaviour of it and related attributes in a 2016 comment on GitHub:
Ah yes, we've got three entry points. I.. think this is how they work:
First, #[start], the receiver of int argc and char **argv. This is literally the symbol main (or what is called by that symbol generated in the compiler).
Next, there's #[lang = "start"]. If no #[start] exists in the crate graph then the compiler generates a main function that calls this. This functions receives argc/argv along with a third argument that is a function pointer to the #[main] function (defined below). Importantly, #[lang = "start"] can be located in a library. For example it's located in the standard library (libstd).
Finally, #[main], the main function for an executable. This is passed no arguments and is called by #[lang = "start"] (if it decides to). The standard library uses this to initialize itself and then call the Rust program. This, if not specified, defaults to fn main at the top.
So to answer your question, this isn't the same as #[start]. To answer your other (possibly not yet asked) question, yes we have too many entry points.
The question is simple, how do we make es6 modules act like the ImportScript function used on the web browser.
Explanation
The main reason is to soften the blow for developers as they change their code from es5 syntax to es6 so that the transition does not blow up your code the moment you make the change and find out there are a thousand errors due to missing inclusions. It also give's people the option to stay as is indefinitely if you don't want to make the full change at all.
Desired output
ImportScript(A file path/'s); can be applied globally(implicitly) across subsequently required code and vise-verse inside a main file to avoid explicit inclusion in all files.
ES6 Inclusion
This still does not ignore the fact that all your libraries will depend on modules format as well. So it is inevitable that we will still have to include the export statement in every file we need to require. However, this should not limit us to the ability to have a main file that interconnects them all without having to explicitly add includes to every file whenever you need a certain functionality.
DISCLAIMER'S
(Numbered):
(Security) I understand there are many reasons that modules exist and going around them is not advisable for security reasons/load times. However I am not sure about the risk (if any) of even using a method like "eval()" to include such scripts if you are only doing it once at the start of an applications life and to a constant value that does not accept external input. The theory is that if an external entity is able to change the initial state of your program as is launched then your system has already been compromised. So as it is I think the whole argument around Globalization vs modules boils down to the project being done(security/speed needed) and preference/risk.
(Not for everyone) This is a utility I am not implying that everyone uses this
(Already published works) I have searched a lot for this functionality but I am not infallible to err. So If a simple usage of this has already been done that follows this specification(or simpler), I'd love to know how/where I can attain such code. Then I will promptly mark that as the answer or just remove this thread entirely
Example Code
ES5 Way
const fs = require('fs');
let path = require('path');
/* only accepts the scripts with global variables and functions and
does not work with classes unless declared as a var.
*/
function include(f) {
eval.apply(global, [fs.readFileSync(f).toString()])
}
Main file Concept example:
ImportScript("filePath1");loaded first
ImportScript("filePath2");loaded second
ImportScript("filePath3");loaded third
ImportScript("filePath4");loaded fourth
ImportScript("filePath5");loaded fifth
ImportScript("someExternalDependency");sixth
/* where "functionNameFromFile4" is a function defined in
file4 , and "variableFromFile2" is a global dynamic
variable that may change over the lifetime of the
application.
*/
functionNameFromFile4(variableFromFile2);
/* current context has access to previous scripts contexts
and those scripts recognize the current global context as
well in short: All scripts should be able to access
variables and functions from other scripts implicitly
through this , even if they are added after the fact
*/
Typical exported file example (Covers all methods of export via modules):
/*where "varFromFile1" is a dynamic variable created in file1
that may change over the lifetime of the application and "var" is a
variable of type(varFromFile4) being concatenated/added together
with "varFromFile4".
*/
functionNameFromFile4(var){
return var+varFromFile1;
}
//Typical export statement
exportAllHere;
/*
This is just an example and does not cover all usage cases , just
an example of the possible functionality
*/
CONCLUSION
So you still need to export the files as required by the es6 standard , however you only need to import them once in a main file to globalize their functionality across all scripts.
I'm not personally a fan of globalizing all the exports from a module, but here's a little snippet that shows you how one ESM module's exports can be all assigned to the global object:
Suppose you had a simple module called operators.js:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
You can import that module and then assign all of its exported properties to the global object with this:
import * as m from "./operators.js"
for (const [prop, value] of Object.entries(m)) {
global[prop] = value;
}
// can now access the exports globally
add(1, 2);
FYI, I think the syntax:
include("filePath1")
is going to be tough in ESM modules because dynamic imports in an ESM module using import (which is presumably what you would have to use to implement the include() function you show) are asynchronous (they return a promise), not synchronous like require().
I wonder if a bundler or a transpiler would be an option?
There is experimental work in nodejs related to custom loaders here: https://nodejs.org/api/esm.html#hooks.
If you can handle your include() function returning a promise, here's how you put the above code into that function:
async function include(moduleName) {
const m = await import(moduleName);
for (const [prop, value] of Object.entries(m)) {
global[prop] = value;
}
return m;
}
app.js
require('./modules/mod');
modules/mod/mod.js
modules.exports = () => {
require('./modules/secondmodule');
}
Essentially I want the above code to be able to require modules, but using the same context that itself was called from, e.g. another module in the same folder, without having to use relative paths.
I thought module.require() did this, but it seems to give me the same error that require() was after I moved my code into the separate module (mod.js).
edit:
I have since discovered I can use require.parent.module and it seems to be working. Please let me know if this is not advised.
require uses paths that are relative to current module. It's possible to do this by providing require from parent module:
modules.exports = parentRequire => {
parentRequire('./modules/secondmodule');
}
Which is used like:
require('./modules/mod')(require);
It's correct to use relative modules instead because child module shouldn't be aware of the context in which it's evaluated:
require('../secondmodule');
In case mod has to be decoupled from secondmodule, dependencies can be provided to it with some common pattern, e.g. dependency injection or service locator.
Secondary optional answer:
module.exports = () => {
module.parent.require('./modules/secondmodule');
}
I encountered some problems reading the Rust documentation:
In this example, we have three modules again: client, network, and network::client. Following the same steps we did earlier for extracting modules into files, we would create src/client.rs for the client module. For the network module, we would create src/network.rs. But we wouldn’t be able to extract the network::client module into a src/client.rs file because that already exists for the top-level client module! If we could put the code for both the client and network::client modules in the src/client.rs file, Rust wouldn’t have any way to know whether the code was for client or for network::client
Why does Rust need to know the code in client.rs belongs to client or network::client? Can it belong to both?
The compiler has rules about where the source file for an external module can be. Those rules ensure that there aren't two modules that use the same source file.
If you really want to, you can override the rules with a #[path] attribute:
mod client; // defaults to client.rs relative to the current file
mod network {
#[path="client.rs"] // reads the same source as the outer `mod client;`
mod client;
}
However, doing so would lead to duplicate code, i.e. the code in client.rs would be compiled twice, and everything that's defined in client.rs would be defined twice, in two separate modules. It's as if you made network/client.rs an exact copy of client.rs and didn't write the #[path] attribute.
Another thing you can do is provide an alias for a module by reexporting it elsewhere. This can be useful when building a library: it enables you to present an external module hierarchy that is different from the internal module hierarchy.
mod client; // not accessible externally
pub mod network {
pub use client; // network::client::* will refer to the same definitions as client::*
}
For example, with the above, the client module is defined in client.rs, but clients use it through my_crate::network::client.
I've got a struct in a file that begins with this line:
// +build windows
Therefore it will only be built on Windows. However, the part of the application that initializes everything needs to check if it is running on Windows and if so, create an instance of the struct. I have no idea how to do this without breaking things on other platforms.
For example, if the file contains a function newWindowsSpecificThing() and I compile on Linux, the function won't exist because it is defined in a file that isn't being compiled. (And, of course, this will produce an error.)
How do I work around this dilemma?
I think your solution would be to have some method on your struct which is used on all platforms. Look at how the dir_*.go files work for the os package. The func (file *File) readdirnames(n int) (names []string, err error) is available on all platforms by providing it in dir_plan9.go, dir_unix.go and dir_windows.go.
For your problem, I'd take the same approach but with some generic method that does internal work. In your application logic you'd call that function and in your file_unix.go file you'd define that function to do nothing (empty body).
Somewhere you clearly have a function that calls newWindowsSpecificThing(). That should be in a Windows-specific file. If it were, then it wouldn't matter that it isn't available. The fact that you have something "check if it is running on Windows" suggests a if runtime.GOOS == "windows" statement somewhere. Rather than have that, move the entire if into a function that is defined in a Windows-specific file. You'll also need to define that function in a !windows file, which is fine.
As an example from my code, I have a function:
func Setup() *config {
var cfg *config
// setup portable parts of cfg
return PlatformSpecificSetup(cfg)
}
I then have a file marked // +build windows that defines PlatformSpecificSetup() one way, and another marked // +build !windows that defines it another. I never have to check runtime.GOOS and I never have to deal with undefined data types. The config struct itself is defined in those files, so it can have different fields for each platform (as long as they agree enough for Setup()). If I were being more careful, I could create a struct like:
type config struct {
// independent stuff
plat *platformConfig
}
And then just define platformConfig in each platform file, but in practice I've found that more trouble than it's worth.