Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Working with long-running operations

Occasionally, an API may need to expose a method that takes a significant amount of time to complete. In these situations, it's often a poor user experience to simply block while the task runs. It's usually better to return some kind of promise to the user and allow the user to check back later.

The Google Cloud Client Libraries for Rust provide helpers to work with these long-running operations (LROs). This guide shows you how to start LROs and wait for their completion.

Prerequisites

The guide uses the Cloud Storage service to keep the code snippets concrete. The same ideas work for any other service using LROs.

The guide assumes you have an existing Google Cloud project with billing enabled.

For complete setup instructions for the Rust libraries, see Setting up your development environment.

Dependencies

Declare Google Cloud dependencies in your Cargo.toml file:

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning

You'll also need several tokio features:

cargo add tokio --features full,macros

Starting a long-running operation

To start a long-running operation, you'll initialize a client and then make the RPC. But first, add some use declarations to avoid the long package names:

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

Now create the client:

    let client = StorageControl::builder().build().await?;

You'll use rename folder for this example. This operation may take a long time when using large folders, but it is relatively fast with smaller folders.

In the Rust client libraries, each request is represented by a method that returns a request builder. First, call the right method on the client to create the request builder:

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)

The sample functions accept the bucket and folder names as arguments:

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

Make the request and wait for an Operation to be returned. This Operation acts as a promise for the result of the long-running request:

    let operation =
        // ... ...
        .send()
        .await?;

While this request has started the operation in the background, you should wait until the operation completes to determine if it was successful or failed. Continue reading to learn how you use the client library polling loops, or how to write your own.

You can find the full function below.

Automatically polling a long-running operation

To configure automatic polling, prepare the request as you did to start a long-running operation. The difference comes at the end, where instead of sending the request using .send().await you create a Poller and wait until it is done:

        .poller()
        .until_done()
        .await?;

Let's review the code step-by-step. First, introduce the Poller trait in scope via a use declaration:

    use google_cloud_lro::Poller;

Then initialize the client and prepare the request as before:

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)

And then poll until the operation is completed and print the result:

        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

You can find the full function below.

Polling a long-running operation with intermediate results

While .until_done() is convenient, it omits some information: long-running operations may report partial progress via a "metadata" attribute. If your application requires such information, use the poller directly:

    let mut poller = client
        .rename_object()
        /* more stuff */
        .poller();

Then use the poller in a loop:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

Note how this loop explicitly waits before polling again. The polling period depends on the specific operation and its payload. You should consult the service documentation and/or experiment with your own data to determine a good value.

The poller uses a policy to determine what polling errors are transient and may resolve themselves. The Configuring polling policies chapter covers this topic in detail.

You can find the full function below.

Manually polling a long-running operation

In general, we recommend that you use the previous two approaches in your application. Alternatively, you can manually poll a long-running operation, but this can be quite tedious, and it is easy to get the types wrong. If you do need to manually poll a long-running operation, this section walks you through the required steps. You may want to read the Operation message reference documentation, as some of the fields and types are used below.

Recall that you started the long-running operation using the client:

    let mut operation = client
        .rename_folder()
        /* more stuff */
        .send()
        .await?;

Start a loop to poll the operation, and check if the operation completed using the done field:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

In most cases, when the operation is completed it contains a result. However, the field is optional because the service could return done as true and no result. For example, if the operation deletes resources and a successful completion has no return value. In this example, using the Storage service, you can ignore this nuance and assume a value will be present:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

Starting a long-running operation successfully does not guarantee that it will complete successfully. The result may be an error or a valid response. You need to check for both. First check for errors:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

The error type is a Status message type. This does not implement the standard Error interface. You need to manually convert it to a valid error. You can use Error::service to perform this conversion.

Assuming the result is successful, you need to extract the response type. You can find this type in the documentation for the LRO method, or by reading the service API documentation:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

Note that extraction of the value may fail if the type does not match what the service sent.

All types in Google Cloud may add fields and branches in the future. While this is unlikely for a common type such as Operation, it happens frequently for most service messages. The Google Cloud Client Libraries for Rust mark all structs and enums as #[non_exhaustive] to signal that such changes are possible. In this case, you must handle this unexpected case:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

If the operation has not completed, then it may contain some metadata. Some services just include initial information about the request, while other services include partial progress reports. You can choose to extract and report this metadata:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

As the operation has not completed, you need to wait before polling again. Consider adjusting the polling period, maybe using a form of truncated exponential backoff. This example simply polls every 500ms:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

If the operation has not completed, you need to query its status:

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

For simplicity, the example ignores all errors. In your application you may choose to treat only a subset of the errors as non-recoverable, and may want to limit the number of polling attempts if these fail.

You can find the full function below.

What's next

Starting a long-running operation: complete code

Automatically polling a long-running operation: complete code

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

Polling a long-running operation: complete code

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}

Manually polling a long-running operation: complete code

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_storage::model::Folder;
    use google_cloud_storage::model::RenameFolderMetadata;

    let client = StorageControl::builder().build().await?;

    let operation = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .send()
        .await?;
    println!("LRO started, response={operation:?}");

    let mut operation = operation;
    let response: anyhow::Result<Folder> = loop {
        if operation.done {
            match &operation.result {
                None => {
                    break Err(anyhow!("missing result for finished operation"));
                }
                Some(r) => {
                    break match r {
                        longrunning::model::operation::Result::Error(s) => {
                            Err(anyhow!("operation completed with error {s:?}"))
                        }
                        longrunning::model::operation::Result::Response(any) => {
                            let response = any.to_msg::<Folder>()?;
                            Ok(response)
                        }
                        _ => Err(anyhow!("unexpected result branch {r:?}")),
                    };
                }
            }
        }
        if let Some(any) = &operation.metadata {
            let metadata = any.to_msg::<RenameFolderMetadata>()?;
            println!("LRO in progress, metadata={metadata:?}");
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        if let Ok(attempt) = client
            .get_operation()
            .set_name(&operation.name)
            .send()
            .await
        {
            operation = attempt;
        }
    };
    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn automatic(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::Poller;

    let client = StorageControl::builder().build().await?;

    let response = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller()
        .until_done()
        .await?;

    println!("LRO completed, response={response:?}");

    Ok(())
}

pub async fn polling(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {
    use google_cloud_lro::{Poller, PollingResult};

    let client = StorageControl::builder().build().await?;

    let mut poller = client
        .rename_folder()
        .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
        .set_destination_folder_id(dest)
        .poller();

    while let Some(p) = poller.poll().await {
        match p {
            PollingResult::Completed(r) => {
                println!("LRO completed, response={r:?}");
            }
            PollingResult::InProgress(m) => {
                println!("LRO in progress, metadata={m:?}");
            }
            PollingResult::PollingError(e) => {
                println!("Transient error polling the LRO: {e}");
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
    }

    Ok(())
}

pub async fn test(control: &StorageControl, bucket: &str) -> anyhow::Result<()> {
    for id in ["manual/", "automatic/", "polling/"] {
        let folder = control
            .create_folder()
            .set_parent(bucket)
            .set_folder_id(id)
            .send()
            .await?;
        println!("created folder {id}: {folder:?}");
    }
    let bucket_id = bucket.strip_prefix("projects/_/buckets/").ok_or(anyhow!(
        "bad bucket name format {bucket}, should start with `projects/_/buckets/`"
    ))?;
    println!("running manual LRO example");
    manual(bucket_id, "manual", "manual-renamed").await?;
    println!("running automatic LRO example");
    automatic(bucket_id, "automatic", "automatic-renamed").await?;
    println!("running automatic LRO with polling example");
    polling(bucket_id, "polling", "polling-renamed").await?;
    Ok(())
}