Yes, the code shown is potentially vulnerable to timing attack, the simplest form of side-channel attack, to determine the value of condition. More generally, essentially any use of a variable quantity in C or Rust can leak it's value by side-channels, e.g. power analysis.
Among the few uses of a variable quantity that is generally* safe from timing attack in compiled languages (like C or Rust) on modern CPUs are bitwise and arithmetic operators & | ^ ~ + - when operating on integer types; casting among these types; shift (<< >>) of such quantity if the shift count is non-secret (including a public constant shift). Also, reading from or writing to a variable (or a vector at a non-secret offset) is safe.
With this in mind, assuming a and b are of unsigned type, and condition is 0 or 1 and of type ìnt, this code is provably equivalent to the one in the question and is less unlikely to be constant-time:
unsigned ai = a ^ b; unsigned bi = ((-(unsigned)conditional) & ai) ^ a; // b if conditional, a otherwise ai ^= bi; // a if conditional, b otherwise f(ai, bi); g(bi, ai);
This produces the correct result because (-(unsigned)conditional) has all it's bit at 0 if conditional is 0, and all it's bit at 1 if conditional is 1. This is an insurance of the C language.
This solves the data-dependent timing in the code shown, but is still potentially vulnerable to other side-channel attacks, e.g. power analysis. And of course there remains the potential for data-dependent timing inside the code of f and g.
* It's still advisable to check the generated code, especially when using signed quantities. An example is uint64_t j = i where i is an ìnt8_t variable: duration conceivably could depend on the sign of i on some combinations of CPU and compiler.