Implement a minimal, internal, experimental rust_proto_library rule.

The internal design is consistent with other <lang>_proto_library rules. rust_proto_library attaches rust_proto_library_aspect on its `deps` attribute. The aspect traverses the dependency, and when it visits proto_library (detected by ProtoInfo provider) it registers 2 actions:

1) to run protoc with Rust backend to emit gencode
2) to compile the gencode using Rustc

Action (2) gets the Rust proto runtime as an input as well.

Coming in a followup is support and test coverage for proto_library.deps.

PiperOrigin-RevId: 514521285
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index 26b4a32..2c329e6 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -24,4 +24,4 @@
         with:
           check_filenames: true
           skip: ./.git,./third_party,./conformance/third_party,*.snk,*.pb,*.pb.cc,*.pb.h,./src/google/protobuf/testdata,./objectivec/Tests,./python/compatibility_tests/v2.5.0/tests/google/protobuf/internal,./.github/workflows/codespell.yml
-          ignore_words_list: "alow,alse,atleast,ba,chec,cleare,copyable,cloneable,dedup,dur,errorprone,falsy,files',fo,fundementals,hel,importd,inout,leapyear,nd,nin,ois,ons,parseable,process',ro,te,testof,ue,unparseable,wasn,wee,gae,keyserver,objext,od,optin,streem,sur,falsy"
+          ignore_words_list: "alow,alse,atleast,ba,chec,cleare,copyable,cloneable,crate,dedup,dur,errorprone,falsy,files',fo,fundementals,hel,importd,inout,leapyear,nd,nin,ois,ons,parseable,process',ro,te,testof,ue,unparseable,wasn,wee,gae,keyserver,objext,od,optin,streem,sur,falsy"
diff --git a/WORKSPACE b/WORKSPACE
index 2301093..af7d63a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -136,4 +136,4 @@
 
 load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains")
 rules_rust_dependencies()
-rust_register_toolchains()
+rust_register_toolchains(edition = "2021")
diff --git a/rust/BUILD b/rust/BUILD
new file mode 100644
index 0000000..bd2eeb7
--- /dev/null
+++ b/rust/BUILD
@@ -0,0 +1,19 @@
+# Protobuf Rust runtime packages.
+
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//src/google/protobuf:__subpackages__"])
+
+rust_library(
+    name = "protobuf",
+    srcs = ["lib.rs"],
+)
+
+# TODO(b/270125787): Move to the right location once rust_proto_library is no longer experimental.
+proto_lang_toolchain(
+    name = "proto_lang_toolchain",
+    command_line = "--rust_out=experimental-codegen=enabled:$(OUT)",
+    progress_message = "Generating Rust proto_library %{label}",
+    runtime = ":protobuf",
+    visibility = ["//visibility:public"],
+)
diff --git a/rust/BUILD.bazel b/rust/BUILD.bazel
deleted file mode 100644
index 5efdab5..0000000
--- a/rust/BUILD.bazel
+++ /dev/null
@@ -1,10 +0,0 @@
-# Protobuf Rust runtime packages.
-
-load("@rules_rust//rust:defs.bzl", "rust_library")
-
-package(default_visibility = ["//src/google/protobuf:__subpackages__"])
-
-rust_library(
-    name = "protobuf",
-    srcs = ["lib.rs"],
-)
diff --git a/rust/defs.bzl b/rust/defs.bzl
new file mode 100644
index 0000000..11f8f71
--- /dev/null
+++ b/rust/defs.bzl
@@ -0,0 +1,242 @@
+"""This file implements an experimental, do-not-use-kind of rust_proto_library.
+
+Disclaimer: This project is experimental, under heavy development, and should not
+be used yet."""
+
+# buildifier: disable=bzl-visibility
+load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVariantInfo")
+
+# buildifier: disable=bzl-visibility
+load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action")
+load("@rules_rust//rust:defs.bzl", "rust_common")
+
+proto_common = proto_common_do_not_use
+
+RustProtoInfo = provider(
+    doc = "Rust protobuf provider info",
+    fields = {
+        "dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " +
+                            "transitive dependencies)",
+    },
+)
+
+def _generate_rust_gencode(
+        ctx,
+        proto_info,
+        proto_lang_toolchain):
+    """Generates Rust gencode
+
+        This function uses proto_common APIs and a ProtoLangToolchain to register an action
+        that invokes protoc with the right flags.
+    Args:
+        ctx (RuleContext): current rule context
+        proto_info (ProtoInfo): ProtoInfo of the proto_library target for which we are generating
+                    gencode
+        proto_lang_toolchain (ProtoLangToolchainInfo): proto lang toolchain for Rust
+    Returns:
+        rs_outputs ([File]): generated Rust source files
+    """
+    actions = ctx.actions
+    rs_outputs = proto_common.declare_generated_files(
+        actions = actions,
+        proto_info = proto_info,
+        extension = ".pb.rs",
+    )
+
+    proto_common.compile(
+        actions = ctx.actions,
+        proto_info = proto_info,
+        generated_files = rs_outputs,
+        proto_lang_toolchain_info = proto_lang_toolchain,
+        plugin_output = ctx.bin_dir.path,
+    )
+    return rs_outputs
+
+def _get_crate_info(providers):
+    for provider in providers:
+        if hasattr(provider, "name"):
+            return provider
+    fail("Couldn't find a CrateInfo in the list of providers")
+
+def _get_dep_info(providers):
+    for provider in providers:
+        if hasattr(provider, "direct_crates"):
+            return provider
+    fail("Couldn't find a DepInfo in the list of providers")
+
+def _get_cc_info(providers):
+    for provider in providers:
+        if hasattr(provider, "linking_context"):
+            return provider
+    fail("Couldn't find a CcInfo in the list of providers")
+
+def _compile_rust(ctx, attr, src, extra_srcs, deps):
+    """Compiles a Rust source file.
+
+    Eventually this function could be upstreamed into rules_rust and be made present in rust_common.
+
+    Args:
+      ctx (RuleContext): The rule context.
+      attr (Attrs): The current rule's attributes (`ctx.attr` for rules, `ctx.rule.attr` for aspects)
+      src (File): The crate root source file to be compiled.
+      extra_srcs ([File]): Additional source files to include in the crate.
+      deps (List[DepVariantInfo]): A list of dependencies needed.
+
+    Returns:
+      A DepVariantInfo provider.
+    """
+    toolchain = ctx.toolchains["@rules_rust//rust:toolchain"]
+    output_hash = repr(hash(src.path))
+
+    # TODO(b/270124215): Use the import! macro once available
+    crate_name = ctx.label.name.replace("-", "_")
+
+    lib_name = "{prefix}{name}-{lib_hash}{extension}".format(
+        prefix = "lib",
+        name = crate_name,
+        lib_hash = output_hash,
+        extension = ".rlib",
+    )
+
+    rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format(
+        prefix = "lib",
+        name = crate_name,
+        lib_hash = output_hash,
+        extension = ".rmeta",
+    )
+
+    lib = ctx.actions.declare_file(lib_name)
+    rmeta = ctx.actions.declare_file(rmeta_name)
+
+    providers = rustc_compile_action(
+        ctx = ctx,
+        attr = attr,
+        toolchain = toolchain,
+        crate_info = rust_common.create_crate_info(
+            name = crate_name,
+            type = "rlib",
+            root = src,
+            srcs = depset([src] + extra_srcs),
+            deps = depset(deps),
+            proc_macro_deps = depset([]),
+            aliases = {},
+            output = lib,
+            metadata = rmeta,
+            edition = "2021",
+            is_test = False,
+            rustc_env = {},
+            compile_data = depset([]),
+            compile_data_targets = depset([]),
+            owner = ctx.label,
+        ),
+        output_hash = output_hash,
+    )
+
+    return DepVariantInfo(
+        crate_info = _get_crate_info(providers),
+        dep_info = _get_dep_info(providers),
+        cc_info = _get_cc_info(providers),
+        build_info = None,
+    )
+
+def _rust_proto_aspect_impl(target, ctx):
+    if ProtoInfo not in target:
+        return None
+
+    proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo]
+
+    gencode = _generate_rust_gencode(ctx, target[ProtoInfo], proto_lang_toolchain)
+
+    runtime = proto_lang_toolchain.runtime
+    dep_variant_info_for_runtime = DepVariantInfo(
+        crate_info = runtime[CrateInfo] if CrateInfo in runtime else None,
+        dep_info = runtime[DepInfo] if DepInfo in runtime else None,
+        cc_info = runtime[CcInfo] if CcInfo in runtime else None,
+        build_info = None,
+    )
+
+    dep_variant_info = _compile_rust(
+        ctx = ctx,
+        attr = ctx.rule.attr,
+        src = gencode[0],
+        extra_srcs = gencode[1:],
+        deps = [dep_variant_info_for_runtime],
+    )
+    return [RustProtoInfo(
+        dep_variant_info = dep_variant_info,
+    )]
+
+rust_proto_library_aspect = aspect(
+    implementation = _rust_proto_aspect_impl,
+    attr_aspects = ["deps"],
+    attrs = {
+        "_cc_toolchain": attr.label(
+            doc = (
+                "In order to use find_cc_toolchain, your rule has to depend " +
+                "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
+                "docs for details."
+            ),
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+        "_collect_cc_coverage": attr.label(
+            default = Label("@rules_rust//util:collect_coverage"),
+            executable = True,
+            cfg = "exec",
+        ),
+        "_error_format": attr.label(
+            default = Label("@rules_rust//:error_format"),
+        ),
+        "_extra_exec_rustc_flag": attr.label(
+            default = Label("@rules_rust//:extra_exec_rustc_flag"),
+        ),
+        "_extra_exec_rustc_flags": attr.label(
+            default = Label("@rules_rust//:extra_exec_rustc_flags"),
+        ),
+        "_extra_rustc_flag": attr.label(
+            default = Label("@rules_rust//:extra_rustc_flag"),
+        ),
+        "_extra_rustc_flags": attr.label(
+            default = Label("@rules_rust//:extra_rustc_flags"),
+        ),
+        "_process_wrapper": attr.label(
+            doc = "A process wrapper for running rustc on all platforms.",
+            default = Label("@rules_rust//util/process_wrapper"),
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
+        "_proto_lang_toolchain": attr.label(
+            default = Label("//rust:proto_lang_toolchain"),
+        ),
+    },
+    fragments = ["cpp"],
+    host_fragments = ["cpp"],
+    toolchains = [
+        str(Label("@rules_rust//rust:toolchain")),
+        "@bazel_tools//tools/cpp:toolchain_type",
+    ],
+    incompatible_use_toolchain_transition = True,
+)
+
+def _rust_proto_library_impl(ctx):
+    deps = ctx.attr.deps
+    if not deps:
+        fail("Exactly 1 dependency in `deps` attribute expected, none were provided.")
+    if len(deps) > 1:
+        fail("Exactly 1 dependency in `deps` attribute expected, too many were provided.")
+
+    dep = deps[0]
+    rust_proto_info = dep[RustProtoInfo]
+    dep_variant_info = rust_proto_info.dep_variant_info
+    return [dep_variant_info.crate_info, dep_variant_info.dep_info, dep_variant_info.cc_info]
+
+rust_proto_library = rule(
+    implementation = _rust_proto_library_impl,
+    attrs = {
+        "deps": attr.label_list(
+            mandatory = True,
+            providers = [ProtoInfo],
+            aspects = [rust_proto_library_aspect],
+        ),
+    },
+)
diff --git a/rust/lib.rs b/rust/lib.rs
index 8974efa..d860245 100644
--- a/rust/lib.rs
+++ b/rust/lib.rs
@@ -31,3 +31,6 @@
 //! Rust Protobuf Runtime
 
 // Not yet implemented.
+
+// TODO(b/270138878): Remove once we have real logic in the runtime.
+pub fn do_nothing() {}
diff --git a/rust/test/BUILD b/rust/test/BUILD
new file mode 100644
index 0000000..2b5bbec
--- /dev/null
+++ b/rust/test/BUILD
@@ -0,0 +1,16 @@
+load("//rust:defs.bzl", "rust_proto_library")
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+rust_proto_library(
+    name = "hello_world_rs_proto",
+    testonly = True,
+    deps = ["//third_party/protobuf:unittest_proto"],
+)
+
+rust_test(
+    name = "hello_world_test",
+    srcs = ["hello_world_test.rs"],
+    # TODO(b/270274576): Enable testing on arm once we have a Rust Arm toolchain.
+    tags = ["not_build:arm"],
+    deps = [":hello_world_rs_proto"],
+)
diff --git a/rust/test/hello_world_test.rs b/rust/test/hello_world_test.rs
new file mode 100644
index 0000000..24cad65
--- /dev/null
+++ b/rust/test/hello_world_test.rs
@@ -0,0 +1,35 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+fn main() {
+    // This is currently just a smoke test checking that we can generate gencode, compile it, and
+    // link the test binary.
+    let _test_all_types: unittest_proto::TestAllTypes;
+}
diff --git a/rust/test/rust_proto_library_unit_test/BUILD b/rust/test/rust_proto_library_unit_test/BUILD
new file mode 100644
index 0000000..de7da61
--- /dev/null
+++ b/rust/test/rust_proto_library_unit_test/BUILD
@@ -0,0 +1,3 @@
+load(":rust_proto_library_unit_test.bzl", "rust_proto_library_unit_test")
+
+rust_proto_library_unit_test(name = "rust_proto_library_unit_test")
diff --git a/rust/test/rust_proto_library_unit_test/defs.bzl b/rust/test/rust_proto_library_unit_test/defs.bzl
new file mode 100644
index 0000000..81741aa
--- /dev/null
+++ b/rust/test/rust_proto_library_unit_test/defs.bzl
@@ -0,0 +1,19 @@
+"""Support for rust_proto_library_aspect unit-tests."""
+
+load("//rust:defs.bzl", "RustProtoInfo", "rust_proto_library_aspect")
+
+ActionsInfo = provider(
+    doc = ("A provider that exposes what actions were registered by rust_proto_library_aspect " +
+           "on proto_libraries."),
+    fields = {"actions": "List[Action]: actions registered on proto_libraries."},
+)
+
+def _attach_aspect_impl(ctx):
+    return [ctx.attr.dep[RustProtoInfo], ActionsInfo(actions = ctx.attr.dep.actions)]
+
+attach_aspect = rule(
+    implementation = _attach_aspect_impl,
+    attrs = {
+        "dep": attr.label(aspects = [rust_proto_library_aspect]),
+    },
+)
diff --git a/rust/test/rust_proto_library_unit_test/rust_proto_library_unit_test.bzl b/rust/test/rust_proto_library_unit_test/rust_proto_library_unit_test.bzl
new file mode 100644
index 0000000..78c2e1e
--- /dev/null
+++ b/rust/test/rust_proto_library_unit_test/rust_proto_library_unit_test.bzl
@@ -0,0 +1,61 @@
+"""This module contains unit tests for rust_proto_library and its aspect."""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load(":defs.bzl", "ActionsInfo", "attach_aspect")
+
+def _find_action_with_mnemonic(actions, mnemonic):
+    action = [a for a in actions if a.mnemonic == mnemonic]
+    if not action:
+        fail("Couldn't find action with mnemonic {} among {}".format(mnemonic, actions))
+    return action[0]
+
+def _find_rust_lib_input(inputs, target_name):
+    inputs = inputs.to_list()
+    input = [i for i in inputs if i.basename.startswith("lib" + target_name) and
+                                  (i.basename.endswith(".rlib") or i.basename.endswith(".rmeta"))]
+    if not input:
+        fail("Couldn't find lib{}-<hash>.rlib or lib{}-<hash>.rmeta among {}".format(
+            target_name,
+            target_name,
+            [i.basename for i in inputs],
+        ))
+    return input[0]
+
+def _rust_compilation_action_has_runtime_as_input_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    target_under_test = analysistest.target_under_test(env)
+    actions = target_under_test[ActionsInfo].actions
+    rustc_action = _find_action_with_mnemonic(actions, "Rustc")
+    _find_rust_lib_input(rustc_action.inputs, "protobuf")
+    asserts.true(env, rustc_action.outputs.to_list()[0].path.endswith(".rlib"))
+
+    return analysistest.end(env)
+
+rust_compilation_action_has_runtime_as_input_test = analysistest.make(
+    _rust_compilation_action_has_runtime_as_input_test_impl,
+)
+
+def _test_rust_compilation_action_has_runtime_as_input():
+    native.proto_library(name = "some_proto", srcs = ["some_proto.proto"])
+    attach_aspect(name = "some_proto_with_aspect", dep = ":some_proto")
+
+    rust_compilation_action_has_runtime_as_input_test(
+        name = "rust_compilation_action_has_runtime_as_input_test",
+        target_under_test = ":some_proto_with_aspect",
+        # TODO(b/270274576): Enable testing on arm once we have a Rust Arm toolchain.
+        tags = ["not_build:arm"],
+    )
+
+def rust_proto_library_unit_test(name):
+    """Sets up rust_proto_library_unit_test test suite.
+
+    Args:
+      name: name of the test suite"""
+    _test_rust_compilation_action_has_runtime_as_input()
+
+    native.test_suite(
+        name = name,
+        tests = [
+            ":rust_compilation_action_has_runtime_as_input_test",
+        ],
+    )
diff --git a/rust/test/rust_proto_library_unit_test/some_proto.proto b/rust/test/rust_proto_library_unit_test/some_proto.proto
new file mode 100644
index 0000000..1cc4a52
--- /dev/null
+++ b/rust/test/rust_proto_library_unit_test/some_proto.proto
@@ -0,0 +1,39 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file exists because of the design of the Starlark unit testing
+// framework. We call these tests unittests, but Blaze still sees them as full
+// builds. And while Blaze doesn't build the test-setup targets unless it really
+// needs to, it checks that all input files are present. Therefore, we need
+// these "empty" files to be present.
+
+syntax = "proto2";
+
+package third_party_protobuf_rust_test_rust_proto_library_unit_test;
diff --git a/src/google/protobuf/compiler/rust/generator.cc b/src/google/protobuf/compiler/rust/generator.cc
index b9e7ccb..a01a82a 100644
--- a/src/google/protobuf/compiler/rust/generator.cc
+++ b/src/google/protobuf/compiler/rust/generator.cc
@@ -75,9 +75,20 @@
   auto outfile = absl::WrapUnique(
       generator_context->Open(absl::StrCat(basename, ".pb.rs")));
 
-  google::protobuf::io::Printer(outfile.get()).Emit(R"cc(
-    // TODO: Generate Bindings
-  )cc");
+  google::protobuf::io::Printer p(outfile.get());
+  // TODO(b/270138878): Remove `do_nothing` import once we have real logic. This
+  // is there only to smoke test rustc actions in rust_proto_library.
+  p.Emit(R"rs(
+#[allow(unused_imports)]
+    use protobuf::do_nothing;
+  )rs");
+  for (int i = 0; i < file->message_type_count(); ++i) {
+    // TODO(b/270138878): Implement real logic
+    p.Emit({{"Msg", file->message_type(i)->name()}}, R"rs(
+                    pub struct $Msg$ {}
+                  )rs");
+  }
+
   return true;
 }