How to make this special binary-search algorithm more rusty?
As always, the interface provided by LeetCode is atrocious.  In order to make your code Rusty, I recommend writing your own interface first and then call it within the implementation of LeetCode's interface.  Here's why.
The full interface of the search function, imposed by LeetCode, is
impl Solution {
    pub fn search(nums: Vec&lt;i32&gt;, target: i32) -&gt; i32 {
        // ...
    }
}

Unfortunately, every single part — no exaggeration here — of the interface can be improved.

struct Solution is completely unnecessary in Rust.  It is much more idiomatic to define search as a free function or a trait method.

The name search does not indicate the assumptions on nums clearly.  I prefer something along the lines of rotated_binary_search.

Since the function only reads the contents of nums, there is no need to take ownership of the argument.  Moreover, the function can be made more generic by taking a slice instead of a Vec — see Why is it discouraged to accept a reference to a String (&amp;String), Vec (&amp;Vec), or Box (&amp;Box) as a function argument?.

The i32s in the types of nums and target aren't unidiomatic per se, but since the same algorithm applies to any type with a total order, a generic type T: Ord is even better.

The return type i32 is problematic for two reasons:

Rust uses usize for indexes, not i32, so returning i32 introduces gratuitous type conversions.

The absence of the target value is signaled by -1, which is error-prone, as the caller may easily forget to check the return value against -1.


Instead, Option&lt;usize&gt; should be used so as to take advantage of the type system.


Here's the improved interface:
pub fn rotated_binary_search&lt;T: Ord&gt;(nums: &amp;[T], target: &amp;T) -&gt; Option&lt;usize&gt; {
    // ...
}


The implementation is of much better quality than the interface.  The last line return -1; can be simplified to -1 (without the semicolon), as the last expression in a function is implicitly the return value.  Then, we can adjust the returns to the new interface — return Some(index) and return None surely read better than -1, right? :)
Now, let's look at the implementation of binary_search (and binary_search_for) in the standard library for inspiration: (ll. 2077–2176, documentation and attributes omitted for brevity)
pub fn binary_search(&amp;self, x: &amp;T) -&gt; Result&lt;usize, usize&gt;
where
    T: Ord,
{
    self.binary_search_by(|p| p.cmp(x))
}

pub fn binary_search_by&lt;'a, F&gt;(&amp;'a self, mut f: F) -&gt; Result&lt;usize, usize&gt;
where
    F: FnMut(&amp;'a T) -&gt; Ordering,
{
    let s = self;
    let mut size = s.len();
    if size == 0 {
        return Err(0);
    }
    let mut base = 0usize;
    while size &gt; 1 {
        let half = size / 2;
        let mid = base + half;
        // SAFETY: the call is made safe by the following inconstants:
        // - `mid &gt;= 0`: by definition
        // - `mid &lt; size`: `mid = size / 2 + size / 4 + size / 8 ...`
        let cmp = f(unsafe { s.get_unchecked(mid) });
        base = if cmp == Greater { base } else { mid };
        size -= half;
    }
    // SAFETY: base is always in [0, size) because base &lt;= mid.
    let cmp = f(unsafe { s.get_unchecked(base) });
    if cmp == Equal { Ok(base) } else { Err(base + (cmp == Less) as usize) }
}

The major difference is the use of a (base, size) pair instead of a (low, high) pair, which greatly simplifies the control flow.  You can also use unsafe to eliminate bound checks, but I wouldn't recommend that except when performance is absolutely critical.