oh, so my assumption was wrong. it's not the rp2040 microcontrollers, but the ARM64 processors, with which I don't have personal experiences.
technically, every targets can run "bare metal", even for x86_64, we have the x86_64-unknown-none target, but when people talk about bare metal targets, they commonly refer to microcontrollers that are very weak in terms of computing power. for platforms like AArch64, I think terms like "unikernel" are more commonly used than "bare metal" application.
for the rpi 4, I did find an existing PAC on crates.io, and that's it. compared to the cortex-m or riscv based microcontrollers, the ecosystem for bare metal AArch64 targets in rust simply isn't there (yet?).
you can write an i2c driver using the PAC, but you also need all other low level support code to create a full fledged application, such as a custom linker script, the boot routine, the stack and heap initialization, the interrupt handling, etc.
it's possible, e.g. you can utilize existing solutions in C/C++, but the developer experience will not be as smooth as for the microcontrollers which has more mature embedded ecosystem in rust.
yes, that's one of the main benefit of rust's advanced type system compared to tranditional embedded languages like C/C++.
this is the same convention used in the ecosystem for bare metal targets (most microcontrollers). typically, you have a PAC that is generated from svd files provided by the chip vendors, which contains the register definition for the peripherals. and here comes the important bit: each individual configurable hardware resource is represented by a distinct type. these types are typically zero sized and does not have runtime overhead at all, but they do enable the higher level crates to leverage the rust type system to provide a ergonomic yet very safe APIs.
the documentation of svd2rust contains the details how the generated API works.