How to share peripherals using STM32 hal crates?

I'm trying to find a proper design of peripherals setup so that I can split the code into multiple functions.
Example code:

#![no_std]
#![no_main]

extern crate panic_halt; 

use cortex_m_rt::entry;
use stm32f4xx_hal as hal;
use hal::stm32;
use hal::gpio::*;

struct BoardSetup {
    gpioe: stm32f4xx_hal::gpio::gpioe::Parts
}

impl BoardSetup {
    pub fn new() -> Self {
        let p = stm32::Peripherals::take().unwrap();

        let gpioe = p.GPIOE.split();
        

        BoardSetup {
            gpioe
        }
    }

    pub fn setup1(self) {
        self.gpioe.pe9.into_alternate_af1().internal_pull_up(true);
    }

    pub fn setup2(self) {
        self.gpioe.pe11.into_alternate_af1().internal_pull_up(true);
    }

    pub fn setup3(self) -> Self {
        self.gpioe.pe11.into_alternate_af1().internal_pull_up(true);
        self
    }
}

#[entry]
fn main() -> ! {
    let mut setup = BoardSetup::new();

    setup.setup1();
    setup.setup2();
    setup = setup.setup3();

    loop {
    }
}

It does not compile:

error[E0382]: use of moved value: `self`
  --> src/main.rs:37:9
   |
36 |         self.gpioe.pe11.into_alternate_af1().internal_pull_up(true);
   |         --------------- value moved here
37 |         self
   |         ^^^^ value used here after partial move
   |
   = note: move occurs because `self.gpioe.pe11` has type `stm32f4xx_hal::gpio::gpioe::PE11<stm32f4xx_hal::gpio::Input<stm32f4xx_hal::gpio::Floating>>`, which does not implement the `Copy` trait

error[E0382]: use of moved value: `setup`
  --> src/main.rs:46:5
   |
43 |     let mut setup = BoardSetup::new();
   |         --------- move occurs because `setup` has type `BoardSetup`, which does not implement the `Copy` trait
44 | 
45 |     setup.test1();
   |     ----- value moved here
46 |     setup.test2();
   |     ^^^^^ value used here after move

error[E0382]: use of moved value: `setup`
  --> src/main.rs:47:13
   |
43 |     let mut setup = BoardSetup::new();
   |         --------- move occurs because `setup` has type `BoardSetup`, which does not implement the `Copy` trait
...
46 |     setup.test2();
   |     ----- value moved here
47 |     setup = setup.test3();
   |             ^^^^^ value used here after move

error: aborting due to 3 previous errors

I'm quite new to Rust though I understand why it fails. I struggle finding a valid approach to my problem.
Or is it just impossible within constraints of the design of the HAL crate and I need to pack all setup into one function?

Two things here AFAICS:

  1. You're using self directly in methods of the struct. This means that those methods "consume" the self. This is usually only intended in "convert and return inner" logic. It means you can't use the struct after you call such methods. You should use fn setup1(&mut self)...

  2. It looks very OOP approach to me, Rust is however quite opposite to standard OOP designs. You might want to consider splitting things into data and functionality and not trying to struct everything together. (this is just a general observation tho)

Using &mut self won't work either, as those into_alternate_af1 methods consume their own selfs.

Other than that, I second @almindor's suggestions. Don't bundle all data into one struct. Keep Peripherals in a variable on the stack, move whatever the configuration functions need (and nothing else) into the functions, and return their result.

Other than that, it really depends on what you actually want to achieve. The HAL design certainly doesn't prevent you from splitting your code into functions, but it's going to require an understanding of move semantics, borrowing, generics, and such.

Sure, I can restructure this to just two functions:

pub fn setup1(gpioe: &mut stm32f4xx_hal::gpio::gpioe::Parts) {
    gpioe.pe9.into_alternate_af1().internal_pull_up(true);
}

pub fn setup2(gpioe: &mut stm32f4xx_hal::gpio::gpioe::Parts) {
    gpioe.pe11.into_alternate_af1().internal_pull_up(true);
}

#[entry]
fn main() -> ! {

    let p = stm32::Peripherals::take().unwrap();

    let mut gpioe = p.GPIOE.split();

    setup1(&mut gpioe);
    setup2(&mut gpioe);

    loop {
    }
}

But this does not compile either, again it boils down to into_alternate_af1 consuming self.
Do I need to restructure this so that all setup of gpioe members is in one place?

Yes, that would be one solution. Have one function that consumes gpioe::Parts and returns whatever your application needs from that in its configured form. (Well, you don't need to return it. If you're just interested in the pull-up being set up and you don't need to access the pin's API afterwards, you can just drop it in the setup function, like you're doing now.)

Another solution would be to just take PE9 in setup1, PE11 in the setup2. (I assume that's what the pin types are called, but I'm not familiar with this specific HAL.)

And by the way, this can't work if your setup functions take &mut gpioe::Parts. You can't move stuff out of a struct that you mutably borrowed, which is what the into_alternate_af1 method is doing.

Yeah, though I'm aiming at a solution where all the setup related to a specific device (say, a display) is in one function. The device can use pins from multiple GPIO groups. And then I would have another device, using pins from maybe the same GPIO groups, setup in another function. That's why I want to pass gpioe to the two setup functions.
And my initial approach, with the BoardSetup struct, was another attempt to share the GPIO.

Then I would recommend passing just the required pins into the setup functions. Depending on whether, for example, the display needs specific pins or just any pins, you can make the arguments concrete or generic (how exactly this would work depends on the specifics of the HAL). You can probably get some inspiration from the HAL API itself, as there should be examples for passing a number of supported pins generically to a function in the HAL API itself.

It's also possible to pass a struct of all pins to a setup function and have the setup function take those pins it needs out of the struct. Then you'd have to write a struct for all pins that tracks which pins were taken out, either at runtime (e.g. with Option) or compile-time (with generics). But I think that sounds like more trouble than it's worth.